From d7430131783177352f8f0e7aed8e4093d38231dd Mon Sep 17 00:00:00 2001 From: Tim Haasdyk Date: Wed, 14 Dec 2022 14:21:19 +0100 Subject: [PATCH 01/13] Upload playwright results when workflow fails (#1648) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I removed this if in #1636, because I thought it was unnecessary (of course I only tested the success case in that PR). It's actually pretty crucial 😆, because without it our results don't get saved when there's a test failure. Oops. --- .github/workflows/integrate-and-deploy.yml | 1 + .github/workflows/pull-request.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/integrate-and-deploy.yml b/.github/workflows/integrate-and-deploy.yml index 8e870aa49a..bbad146060 100644 --- a/.github/workflows/integrate-and-deploy.yml +++ b/.github/workflows/integrate-and-deploy.yml @@ -79,6 +79,7 @@ jobs: run: make e2e-tests-ci - name: Upload Playwright test results + if: always() uses: actions/upload-artifact@v3 with: name: test-results diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 8815f67e46..92c369dbba 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -87,6 +87,7 @@ jobs: - name: Upload Playwright test results + if: always() uses: actions/upload-artifact@v3 with: name: test-results From 0c644b879bceb96c2f859fd3d8b290a5f60a0b51 Mon Sep 17 00:00:00 2001 From: laineyhm <56163492+laineyhm@users.noreply.github.com> Date: Thu, 15 Dec 2022 14:21:26 +0700 Subject: [PATCH 02/13] Repair audio seeking (#1646) * Repaired audio seeking and added a corresponding Playwright test --- .../bellows/shared/sound-player.component.ts | 4 ++-- test/e2e/editor-entry.spec.ts | 13 +++++++++++++ test/e2e/pages/editor.page.ts | 4 +++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/angular-app/bellows/shared/sound-player.component.ts b/src/angular-app/bellows/shared/sound-player.component.ts index 869181bfab..e5b3752433 100644 --- a/src/angular-app/bellows/shared/sound-player.component.ts +++ b/src/angular-app/bellows/shared/sound-player.component.ts @@ -16,7 +16,8 @@ export class SoundController implements angular.IController { $onInit(): void { - this.slider = this.$element.find('.seek-slider').get(0) as HTMLInputElement; + this.slider = this.$element[0].querySelector('.seek-slider') as HTMLInputElement; + this.audioElement.currentTime = 0; //So that duration appears immediately once it is available this.audioElement.addEventListener('durationchange', () => { @@ -92,7 +93,6 @@ export class SoundController implements angular.IController { this.playing = !this.playing; if (this.playing) { - this.audioElement.currentTime = 0; this.playAudio(); } else { if(!this.audioElement.paused){ diff --git a/test/e2e/editor-entry.spec.ts b/test/e2e/editor-entry.spec.ts index ef52afad77..11ff4d26ac 100644 --- a/test/e2e/editor-entry.spec.ts +++ b/test/e2e/editor-entry.spec.ts @@ -322,6 +322,19 @@ test.describe('Lexicon E2E Entry Editor and Entries List', () => { await expect(audio.locator(editorPageManager.audioPlayer.downloadButtonSelector)).not.toBeVisible(); }); + + test('Slider is present and updates with seeking', async () => { + await editorPageManager.goto(); + await expect(audio.locator(editorPageManager.audioPlayer.slider)).toBeVisible(); + const slider = audio.locator(editorPageManager.audioPlayer.slider); + let originalTime = (await audio.locator(editorPageManager.audioPlayer.audioProgressTime).innerText()).substring(3, 4); + let bounds = await slider.boundingBox(); + let yMiddle = bounds.y + bounds.height/2; + await editorPageManager.page.mouse.click(bounds.x+200, yMiddle); + await expect(audio.locator(editorPageManager.audioPlayer.audioProgressTime)).toContainText("0:01 / 0:02"); + }); + + test('File upload drop box is displayed when Upload is clicked & not displayed if upload cancelled (manager)', async () => { await editorPageManager.goto(); const dropbox = editorPageManager.entryCard.locator(editorPageManager.dropbox.dragoverFieldSelector); diff --git a/test/e2e/pages/editor.page.ts b/test/e2e/pages/editor.page.ts index 3f5c20cd34..56510160c4 100644 --- a/test/e2e/pages/editor.page.ts +++ b/test/e2e/pages/editor.page.ts @@ -50,7 +50,9 @@ export class EditorPage extends BasePage { playIconSelector: 'i.fa-play', dropdownToggleSelector: 'a.dropdown-toggle', uploadButtonSelector: 'button.upload-audio', - downloadButtonSelector: 'a.buttonAppend' + downloadButtonSelector: 'a.buttonAppend', + slider: 'input.seek-slider', + audioProgressTime: 'span.audio-progress' }; readonly dropbox = { From 164aba0f7af01f43afaafde2fd4b606c04a1a9a9 Mon Sep 17 00:00:00 2001 From: Tim Haasdyk Date: Fri, 16 Dec 2022 11:51:00 +0100 Subject: [PATCH 03/13] Migrate remaining playwright tests (#1643) Completes our migration to playwright and deletes all things protractory. Additionally: - Changes testConstants.json to constants.ts, throwing 3/4 of it away and harnessing the power to TS to get better type safety - Moved the sample_data out of the tests directory, as it isn't used by tests - Moved test/common/usx into test/php, because it's not used by anything else - Merged the test/e2e/shared-files with test/data (previously) test/common to simplify test data - Slimmed down/rework Playwright debug cheat-sheet - Major refactoring + (I think) some decent test stabilisation --- .gitattributes | 1 - .vscode/settings.json | 5 +- Makefile | 9 +- docker-compose.yml | 2 +- docs/DEVELOPER.md | 2 +- package.json | 1 + .../lift/AllFLExFields/AllFLExFields.lift | 0 .../AllFLExFields/AllFLExFields.lift-ranges | 0 .../lift/AllFLExFields/WritingSystems/en.ldml | 0 .../lift/AllFLExFields/WritingSystems/fr.ldml | 0 .../lift/AllFLExFields/WritingSystems/th.ldml | 0 .../lift/AllFLExFields/audio/Kalimba.mp3 | Bin .../lift/AllFLExFields/others/Hydrangeas.jpg | Bin .../lift/AllFLExFields/pictures/Desert.jpg | Bin .../lift/InvalidAttribute.lift | 0 .../lift/InvalidXml.lift | 0 .../lift/OneEntryV0_12.lift | 0 .../lift/liftNotesWithSpansV0_13.lift | 0 .../lift/liftNotesWithoutSpansV0_13.lift | 0 .../lift/liftTwoEntriesCorrectedV0_13.lift | 0 .../lift/liftTwoEntriesModifiedV0_13.lift | 0 .../lift/liftTwoEntriesV0_13.lift | 0 .../lift/liftTwoEntriesWithSpanV0_13.lift | 0 .../lift/lift_13.pdf | Bin .../lift/sampledf.lift | 0 .../lift/tha-food-small.lift | 0 .../lift/tha-food.WeSayConfig | 0 .../lift/tha-food.lift | 0 .../semdom/SemDom_en_sample.xml | 0 test/app/allspecs/after-each.e2e-spec.ts | 44 -- .../app/bellows/bellows-traversal.e2e-spec.ts | 87 --- test/app/bellows/change-password.e2e-spec.ts | 69 -- test/app/bellows/project-settings.e2e-spec.ts | 151 ---- test/app/bellows/projects.e2e-spec.ts | 133 ---- .../reset-forgotten-password.e2e-spec.ts | 129 ---- test/app/bellows/shared/activity.page.ts | 58 -- test/app/bellows/shared/app.frame.ts | 24 - .../bellows/shared/change-password.page.ts | 17 - .../bellows/shared/forgot-password.page.ts | 14 - test/app/bellows/shared/login.page.ts | 58 -- .../app/bellows/shared/mock-upload.element.ts | 8 - test/app/bellows/shared/page-body.element.ts | 5 - .../app/bellows/shared/page-header.element.ts | 16 - .../bellows/shared/project-settings.page.ts | 60 -- test/app/bellows/shared/projects.page.ts | 135 ---- .../app/bellows/shared/reset-password.page.ts | 15 - test/app/bellows/shared/signup.page.ts | 58 -- test/app/bellows/shared/site-admin.page.ts | 56 -- .../bellows/shared/user-management.page.ts | 61 -- test/app/bellows/shared/user-profile.page.ts | 91 --- test/app/bellows/shared/utils.d.ts | 6 - test/app/bellows/shared/utils.ts | 217 ------ test/app/bellows/signup.e2e-spec.ts | 128 ---- test/app/bellows/user-management.e2e-spec.ts | 86 --- test/app/bellows/user-profile.e2e-spec.ts | 226 ------ test/app/chris's protractor api cheatsheet.md | 117 --- test/app/e2eTestConfig.php | 9 - .../lexicon-traversal.e2e-spec.ts | 99 --- .../editor/editor-comments.e2e-spec.ts | 148 ---- .../lexicon/editor/editor-entry.e2e-spec.ts | 654 ---------------- .../lexicon/lexicon-new-project.e2e-spec.ts | 520 ------------- .../settings/config-fields.e2e-spec.ts | 717 ------------------ .../settings/config-input-systems.e2e-spec.ts | 259 ------- .../settings/interface-language.e2e-spec.ts | 40 - .../settings/semantic-domains.e2e-spec.ts | 148 ---- .../lexicon/shared/configuration.page.ts | 257 ------- .../lexicon/shared/editor.page.ts | 476 ------------ .../lexicon/shared/editor.util.ts | 179 ----- .../lexicon/shared/lex-modals.util.ts | 18 - .../lexicon/shared/new-lex-project.page.ts | 99 --- .../lexicon/shared/project-settings.page.ts | 56 -- test/app/protractorConf.js | 66 -- test/app/setupTestEnvironment.php | 319 -------- test/app/testConstants.json | 193 ----- test/app/tsconfig.json | 20 - .../common/TestLexProject/audio/TestAudio.mp3 | Bin 20616 -> 0 bytes .../TestLexProject/pictures/IMG_0214.JPG | Bin 2753 -> 0 bytes .../TestLexProject/pictures/TestImage.png | Bin 5652 -> 0 bytes test/{common => data}/FriedRiceWithPork.jpg | Bin .../{common => data}/MongoTestEnvironment.php | 4 +- test/{common => data}/TestAudio.mp3 | Bin test/{common => data}/TestAudio.wav | Bin test/{common => data}/TestImage.jpg | Bin test/{common => data}/TestImage.png | Bin test/{common => data}/TestImage.tif | Bin test/{common => data}/TestLangProj.7z | Bin .../TestLex2ProjectsOddFolder.zip | Bin test/{common => data}/TestLexNoProject.zip | Bin test/{common => data}/TestLexProject.zip | Bin .../TestLexProjectWithDir.zip | Bin .../dummy_large_file.zip | Bin .../dummy_small_file.zip | Bin test/e2e/change-password.spec.ts | 100 +-- test/e2e/components/audio-player.ts | 23 + test/e2e/components/base-component.ts | 16 + ...rm-modal.component.ts => confirm-modal.ts} | 2 +- test/e2e/components/editor-comment.ts | 54 ++ test/e2e/components/index.ts | 6 + test/e2e/components/notice.component.ts | 17 - test/e2e/components/notice.ts | 12 + test/e2e/components/page-header.component.ts | 25 - test/e2e/components/page-header.ts | 23 + test/e2e/configuration-input-system.spec.ts | 60 ++ test/e2e/constants.ts | 131 ++++ test/e2e/editor-comments.spec.ts | 163 ++++ test/e2e/editor-entry.spec.ts | 675 ++++++++--------- ...nd-caption-is-present-1-chromium-linux.png | Bin 0 -> 60799 bytes test/e2e/example.spec.ts | 50 -- test/e2e/interface-language.spec.ts | 16 +- test/e2e/lexicon-new-project.spec.ts | 144 ++-- test/e2e/pages/activity.page.ts | 2 +- test/e2e/pages/base-page.ts | 51 +- test/e2e/pages/change-password.page.ts | 5 +- test/e2e/pages/configuration-fields.tab.ts | 55 ++ .../pages/configuration-input-systems.tab.ts | 38 + test/e2e/pages/configuration.page.ts | 71 +- test/e2e/pages/editor.page.ts | 201 +++-- test/e2e/pages/entries-list.page.ts | 38 - test/e2e/pages/entry-list.page.ts | 24 + test/e2e/pages/forgot-password.page.ts | 5 +- test/e2e/pages/login.page.ts | 14 +- test/e2e/pages/new-lex-project.page.ts | 7 +- test/e2e/pages/project-settings.page.ts | 12 +- test/e2e/pages/projects.page.ts | 29 +- test/e2e/pages/reset-password.page.ts | 14 + test/e2e/pages/signup.page.ts | 5 +- test/e2e/pages/site-admin.page.ts | 2 +- test/e2e/pages/tabbed-page.ts | 19 + test/e2e/pages/user-profile.page.ts | 2 +- test/e2e/playwright.config.ts | 17 +- .../playwright_guide/debugging_dot_only.png | Bin 25921 -> 0 bytes .../playwright_guide/debugging_dot_skip.png | Bin 16105 -> 0 bytes .../playwright_guide/playwright_cheatsheet.md | 53 +- .../playwright_extension_sidebar.png | Bin 54509 -> 53358 bytes test/e2e/project-settings.spec.ts | 36 +- test/e2e/projects.spec.ts | 63 +- test/e2e/reset-forgotten-password.spec.ts | 124 +++ test/e2e/shared-files/FriedRiceWithPork.jpg | Bin 1009637 -> 0 bytes test/e2e/shared-files/README.txt | 4 - test/e2e/shared-files/TestAudio.mp3 | Bin 21064 -> 0 bytes test/e2e/shared-files/TestImage.png | Bin 5652 -> 0 bytes test/e2e/shared-files/TestLexProject.zip | Bin 29079 -> 0 bytes test/e2e/signup.spec.ts | 72 +- test/e2e/site-traversal.spec.ts | 12 +- test/e2e/testConstants.json | 1 - test/e2e/tsconfig.json | 3 +- test/e2e/types/index.d.ts | 9 - test/e2e/user-profile.spec.ts | 92 +-- test/e2e/utils/TestControl.php | 149 +--- test/e2e/utils/custom-matchers.ts | 53 ++ test/e2e/utils/e2e-users.ts | 10 - test/e2e/utils/fixtures.ts | 105 ++- test/e2e/utils/globalSetup.ts | 12 +- test/e2e/utils/index.ts | 5 + test/e2e/utils/jsonrpc.ts | 36 +- test/e2e/utils/login.ts | 48 +- test/e2e/utils/navigation.ts | 6 - test/e2e/utils/path-utils.ts | 16 + test/e2e/utils/playwright-helpers.ts | 30 - test/e2e/utils/project-utils.ts | 14 + test/e2e/utils/testSetup.ts | 121 +-- test/e2e/utils/types.ts | 15 +- test/e2e/utils/user-tools.ts | 61 +- test/php/TestConfig.php | 2 +- test/{common => php/data}/usx/040MAT.usx | 0 test/{common => php/data}/usx/041MRK.usx | 0 test/{common => php/data}/usx/042LUK.usx | 0 test/{common => php/data}/usx/043JHN.usx | 0 test/{common => php/data}/usx/CEV_PSA001.usx | 0 test/{common => php/data}/usx/Psalm1.usx | 0 test/{common => php/data}/usx/Psalm1b.usx | 0 171 files changed, 1929 insertions(+), 7721 deletions(-) rename {test/common/sample_data => sample_data}/lift/AllFLExFields/AllFLExFields.lift (100%) rename {test/common/sample_data => sample_data}/lift/AllFLExFields/AllFLExFields.lift-ranges (100%) rename {test/common/sample_data => sample_data}/lift/AllFLExFields/WritingSystems/en.ldml (100%) rename {test/common/sample_data => sample_data}/lift/AllFLExFields/WritingSystems/fr.ldml (100%) rename {test/common/sample_data => sample_data}/lift/AllFLExFields/WritingSystems/th.ldml (100%) rename {test/common/sample_data => sample_data}/lift/AllFLExFields/audio/Kalimba.mp3 (100%) rename {test/common/sample_data => sample_data}/lift/AllFLExFields/others/Hydrangeas.jpg (100%) rename {test/common/sample_data => sample_data}/lift/AllFLExFields/pictures/Desert.jpg (100%) rename {test/common/sample_data => sample_data}/lift/InvalidAttribute.lift (100%) rename {test/common/sample_data => sample_data}/lift/InvalidXml.lift (100%) rename {test/common/sample_data => sample_data}/lift/OneEntryV0_12.lift (100%) rename {test/common/sample_data => sample_data}/lift/liftNotesWithSpansV0_13.lift (100%) rename {test/common/sample_data => sample_data}/lift/liftNotesWithoutSpansV0_13.lift (100%) rename {test/common/sample_data => sample_data}/lift/liftTwoEntriesCorrectedV0_13.lift (100%) rename {test/common/sample_data => sample_data}/lift/liftTwoEntriesModifiedV0_13.lift (100%) rename {test/common/sample_data => sample_data}/lift/liftTwoEntriesV0_13.lift (100%) rename {test/common/sample_data => sample_data}/lift/liftTwoEntriesWithSpanV0_13.lift (100%) rename {test/common/sample_data => sample_data}/lift/lift_13.pdf (100%) rename {test/common/sample_data => sample_data}/lift/sampledf.lift (100%) rename {test/common/sample_data => sample_data}/lift/tha-food-small.lift (100%) rename {test/common/sample_data => sample_data}/lift/tha-food.WeSayConfig (100%) rename {test/common/sample_data => sample_data}/lift/tha-food.lift (100%) rename {test/common/sample_data => sample_data}/semdom/SemDom_en_sample.xml (100%) delete mode 100644 test/app/allspecs/after-each.e2e-spec.ts delete mode 100644 test/app/bellows/bellows-traversal.e2e-spec.ts delete mode 100644 test/app/bellows/change-password.e2e-spec.ts delete mode 100644 test/app/bellows/project-settings.e2e-spec.ts delete mode 100644 test/app/bellows/projects.e2e-spec.ts delete mode 100644 test/app/bellows/reset-forgotten-password.e2e-spec.ts delete mode 100644 test/app/bellows/shared/activity.page.ts delete mode 100644 test/app/bellows/shared/app.frame.ts delete mode 100644 test/app/bellows/shared/change-password.page.ts delete mode 100644 test/app/bellows/shared/forgot-password.page.ts delete mode 100644 test/app/bellows/shared/login.page.ts delete mode 100644 test/app/bellows/shared/mock-upload.element.ts delete mode 100644 test/app/bellows/shared/page-body.element.ts delete mode 100644 test/app/bellows/shared/page-header.element.ts delete mode 100644 test/app/bellows/shared/project-settings.page.ts delete mode 100644 test/app/bellows/shared/projects.page.ts delete mode 100644 test/app/bellows/shared/reset-password.page.ts delete mode 100644 test/app/bellows/shared/signup.page.ts delete mode 100644 test/app/bellows/shared/site-admin.page.ts delete mode 100644 test/app/bellows/shared/user-management.page.ts delete mode 100644 test/app/bellows/shared/user-profile.page.ts delete mode 100644 test/app/bellows/shared/utils.d.ts delete mode 100644 test/app/bellows/shared/utils.ts delete mode 100644 test/app/bellows/signup.e2e-spec.ts delete mode 100644 test/app/bellows/user-management.e2e-spec.ts delete mode 100644 test/app/bellows/user-profile.e2e-spec.ts delete mode 100644 test/app/chris's protractor api cheatsheet.md delete mode 100644 test/app/e2eTestConfig.php delete mode 100644 test/app/languageforge/lexicon-traversal.e2e-spec.ts delete mode 100644 test/app/languageforge/lexicon/editor/editor-comments.e2e-spec.ts delete mode 100644 test/app/languageforge/lexicon/editor/editor-entry.e2e-spec.ts delete mode 100644 test/app/languageforge/lexicon/lexicon-new-project.e2e-spec.ts delete mode 100644 test/app/languageforge/lexicon/settings/config-fields.e2e-spec.ts delete mode 100644 test/app/languageforge/lexicon/settings/config-input-systems.e2e-spec.ts delete mode 100644 test/app/languageforge/lexicon/settings/interface-language.e2e-spec.ts delete mode 100644 test/app/languageforge/lexicon/settings/semantic-domains.e2e-spec.ts delete mode 100644 test/app/languageforge/lexicon/shared/configuration.page.ts delete mode 100644 test/app/languageforge/lexicon/shared/editor.page.ts delete mode 100644 test/app/languageforge/lexicon/shared/editor.util.ts delete mode 100644 test/app/languageforge/lexicon/shared/lex-modals.util.ts delete mode 100644 test/app/languageforge/lexicon/shared/new-lex-project.page.ts delete mode 100644 test/app/languageforge/lexicon/shared/project-settings.page.ts delete mode 100644 test/app/protractorConf.js delete mode 100644 test/app/setupTestEnvironment.php delete mode 100644 test/app/testConstants.json delete mode 100644 test/app/tsconfig.json delete mode 100644 test/common/TestLexProject/audio/TestAudio.mp3 delete mode 100644 test/common/TestLexProject/pictures/IMG_0214.JPG delete mode 100644 test/common/TestLexProject/pictures/TestImage.png rename test/{common => data}/FriedRiceWithPork.jpg (100%) rename test/{common => data}/MongoTestEnvironment.php (98%) rename test/{common => data}/TestAudio.mp3 (100%) rename test/{common => data}/TestAudio.wav (100%) rename test/{common => data}/TestImage.jpg (100%) rename test/{common => data}/TestImage.png (100%) rename test/{common => data}/TestImage.tif (100%) rename test/{common => data}/TestLangProj.7z (100%) rename test/{common => data}/TestLex2ProjectsOddFolder.zip (100%) rename test/{common => data}/TestLexNoProject.zip (100%) rename test/{common => data}/TestLexProject.zip (100%) rename test/{common => data}/TestLexProjectWithDir.zip (100%) rename test/{e2e/shared-files => data}/dummy_large_file.zip (100%) rename test/{e2e/shared-files => data}/dummy_small_file.zip (100%) create mode 100644 test/e2e/components/audio-player.ts create mode 100644 test/e2e/components/base-component.ts rename test/e2e/components/{confirm-modal.component.ts => confirm-modal.ts} (90%) create mode 100644 test/e2e/components/editor-comment.ts create mode 100644 test/e2e/components/index.ts delete mode 100644 test/e2e/components/notice.component.ts create mode 100644 test/e2e/components/notice.ts delete mode 100644 test/e2e/components/page-header.component.ts create mode 100644 test/e2e/components/page-header.ts create mode 100644 test/e2e/configuration-input-system.spec.ts create mode 100644 test/e2e/constants.ts create mode 100644 test/e2e/editor-comments.spec.ts create mode 100644 test/e2e/editor-entry.spec.ts-snapshots/Entry-Editor-and-Entries-List-Entry-Editor-Picture-First-picture-and-caption-is-present-1-chromium-linux.png delete mode 100644 test/e2e/example.spec.ts create mode 100644 test/e2e/pages/configuration-fields.tab.ts create mode 100644 test/e2e/pages/configuration-input-systems.tab.ts delete mode 100644 test/e2e/pages/entries-list.page.ts create mode 100644 test/e2e/pages/entry-list.page.ts create mode 100644 test/e2e/pages/reset-password.page.ts create mode 100644 test/e2e/pages/tabbed-page.ts delete mode 100644 test/e2e/playwright_guide/debugging_dot_only.png delete mode 100644 test/e2e/playwright_guide/debugging_dot_skip.png create mode 100644 test/e2e/reset-forgotten-password.spec.ts delete mode 100644 test/e2e/shared-files/FriedRiceWithPork.jpg delete mode 100644 test/e2e/shared-files/README.txt delete mode 100644 test/e2e/shared-files/TestAudio.mp3 delete mode 100644 test/e2e/shared-files/TestImage.png delete mode 100644 test/e2e/shared-files/TestLexProject.zip delete mode 120000 test/e2e/testConstants.json delete mode 100644 test/e2e/types/index.d.ts create mode 100644 test/e2e/utils/custom-matchers.ts delete mode 100644 test/e2e/utils/e2e-users.ts create mode 100644 test/e2e/utils/index.ts delete mode 100644 test/e2e/utils/navigation.ts create mode 100644 test/e2e/utils/path-utils.ts delete mode 100644 test/e2e/utils/playwright-helpers.ts create mode 100644 test/e2e/utils/project-utils.ts rename test/{common => php/data}/usx/040MAT.usx (100%) rename test/{common => php/data}/usx/041MRK.usx (100%) rename test/{common => php/data}/usx/042LUK.usx (100%) rename test/{common => php/data}/usx/043JHN.usx (100%) rename test/{common => php/data}/usx/CEV_PSA001.usx (100%) rename test/{common => php/data}/usx/Psalm1.usx (100%) rename test/{common => php/data}/usx/Psalm1b.usx (100%) diff --git a/.gitattributes b/.gitattributes index 91643d949a..4a4ccdfc9f 100644 --- a/.gitattributes +++ b/.gitattributes @@ -11,7 +11,6 @@ *.php text eol=lf *.cs text eol=lf *.sh text eol=lf -protractor text eol=lf # Denote all files that are truly binary and should not be modified *.png binary diff --git a/.vscode/settings.json b/.vscode/settings.json index 56a2ea36f6..515c3f2af7 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,5 +3,8 @@ "phpcs.composerJsonPath": "src", "php.suggest.basic": false, "git.ignoreLimitWarning": true, - "editor.defaultFormatter": "esbenp.prettier-vscode" + "editor.defaultFormatter": "esbenp.prettier-vscode", + "[typescript]": { + "editor.defaultFormatter": "vscode.typescript-language-features" + } } diff --git a/Makefile b/Makefile index 89323b2e38..7460eb6d80 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,10 @@ start: build docker compose up -d ssl .PHONY: dev -dev: start +dev: start ui-builder + +.PHONY: ui-builder +ui-builder: docker compose up -d ui-builder .PHONY: e2e-tests-ci @@ -16,10 +19,9 @@ e2e-tests-ci: npx playwright install chromium && npx playwright test -c ./test/e2e/playwright.config.ts .PHONY: e2e-tests -e2e-tests: +e2e-tests: ui-builder npm install $(MAKE) playwright-app - docker compose up -d ui-builder npx playwright install chromium && npx playwright test -c ./test/e2e/playwright.config.ts $(params) .PHONY: playwright-app @@ -70,6 +72,7 @@ clean: docker compose down docker system prune -f +.PHONY: clean-test clean-test: cd test/e2e && npx rimraf test-storage-state test-results diff --git a/docker-compose.yml b/docker-compose.yml index 17ece0c2bb..a1af16dff2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -266,7 +266,7 @@ services: - "host.docker.internal:host-gateway" volumes: - ./test/e2e/utils/TestControl.php:/var/www/src/Api/Service/TestControl.php - - ./test/e2e/shared-files:/tmp/e2e-shared-files + - ./test/data:/tmp/e2e-test-data - lf-ui-dist:/var/www/html/dist # needed this volume mapping so changes to partials would be reflected in running app (e.g. HTML) diff --git a/docs/DEVELOPER.md b/docs/DEVELOPER.md index 88fa41ce9d..502021c798 100644 --- a/docs/DEVELOPER.md +++ b/docs/DEVELOPER.md @@ -175,7 +175,7 @@ If you encounter errors such as VSCode cannot find a file in the path "vendor", ### Playwright E2E Test Debugging -Head on over to [Hanna's tutorial on debugging Playwright E2E tests](test/e2e/playwright_guide/playwright_cheatsheet.md) for more information. +Head on over to [Hanna's tutorial on debugging Playwright E2E tests](../test/e2e/playwright_guide/playwright_cheatsheet.md) for more information. ## Style Guides diff --git a/package.json b/package.json index 23336f3891..8c362467d7 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "webpack:prd": "webpack --config webpack-prd.config.js", "compile-test-e2e": "tsc -p test/app", "test-e2e": "npx playwright test -c ./test/e2e/playwright.config.ts", + "test-e2e-report": "npx playwright show-report ./test/e2e/test-results/_html-report", "prepare": "husky install" }, "license": "MIT", diff --git a/test/common/sample_data/lift/AllFLExFields/AllFLExFields.lift b/sample_data/lift/AllFLExFields/AllFLExFields.lift similarity index 100% rename from test/common/sample_data/lift/AllFLExFields/AllFLExFields.lift rename to sample_data/lift/AllFLExFields/AllFLExFields.lift diff --git a/test/common/sample_data/lift/AllFLExFields/AllFLExFields.lift-ranges b/sample_data/lift/AllFLExFields/AllFLExFields.lift-ranges similarity index 100% rename from test/common/sample_data/lift/AllFLExFields/AllFLExFields.lift-ranges rename to sample_data/lift/AllFLExFields/AllFLExFields.lift-ranges diff --git a/test/common/sample_data/lift/AllFLExFields/WritingSystems/en.ldml b/sample_data/lift/AllFLExFields/WritingSystems/en.ldml similarity index 100% rename from test/common/sample_data/lift/AllFLExFields/WritingSystems/en.ldml rename to sample_data/lift/AllFLExFields/WritingSystems/en.ldml diff --git a/test/common/sample_data/lift/AllFLExFields/WritingSystems/fr.ldml b/sample_data/lift/AllFLExFields/WritingSystems/fr.ldml similarity index 100% rename from test/common/sample_data/lift/AllFLExFields/WritingSystems/fr.ldml rename to sample_data/lift/AllFLExFields/WritingSystems/fr.ldml diff --git a/test/common/sample_data/lift/AllFLExFields/WritingSystems/th.ldml b/sample_data/lift/AllFLExFields/WritingSystems/th.ldml similarity index 100% rename from test/common/sample_data/lift/AllFLExFields/WritingSystems/th.ldml rename to sample_data/lift/AllFLExFields/WritingSystems/th.ldml diff --git a/test/common/sample_data/lift/AllFLExFields/audio/Kalimba.mp3 b/sample_data/lift/AllFLExFields/audio/Kalimba.mp3 similarity index 100% rename from test/common/sample_data/lift/AllFLExFields/audio/Kalimba.mp3 rename to sample_data/lift/AllFLExFields/audio/Kalimba.mp3 diff --git a/test/common/sample_data/lift/AllFLExFields/others/Hydrangeas.jpg b/sample_data/lift/AllFLExFields/others/Hydrangeas.jpg similarity index 100% rename from test/common/sample_data/lift/AllFLExFields/others/Hydrangeas.jpg rename to sample_data/lift/AllFLExFields/others/Hydrangeas.jpg diff --git a/test/common/sample_data/lift/AllFLExFields/pictures/Desert.jpg b/sample_data/lift/AllFLExFields/pictures/Desert.jpg similarity index 100% rename from test/common/sample_data/lift/AllFLExFields/pictures/Desert.jpg rename to sample_data/lift/AllFLExFields/pictures/Desert.jpg diff --git a/test/common/sample_data/lift/InvalidAttribute.lift b/sample_data/lift/InvalidAttribute.lift similarity index 100% rename from test/common/sample_data/lift/InvalidAttribute.lift rename to sample_data/lift/InvalidAttribute.lift diff --git a/test/common/sample_data/lift/InvalidXml.lift b/sample_data/lift/InvalidXml.lift similarity index 100% rename from test/common/sample_data/lift/InvalidXml.lift rename to sample_data/lift/InvalidXml.lift diff --git a/test/common/sample_data/lift/OneEntryV0_12.lift b/sample_data/lift/OneEntryV0_12.lift similarity index 100% rename from test/common/sample_data/lift/OneEntryV0_12.lift rename to sample_data/lift/OneEntryV0_12.lift diff --git a/test/common/sample_data/lift/liftNotesWithSpansV0_13.lift b/sample_data/lift/liftNotesWithSpansV0_13.lift similarity index 100% rename from test/common/sample_data/lift/liftNotesWithSpansV0_13.lift rename to sample_data/lift/liftNotesWithSpansV0_13.lift diff --git a/test/common/sample_data/lift/liftNotesWithoutSpansV0_13.lift b/sample_data/lift/liftNotesWithoutSpansV0_13.lift similarity index 100% rename from test/common/sample_data/lift/liftNotesWithoutSpansV0_13.lift rename to sample_data/lift/liftNotesWithoutSpansV0_13.lift diff --git a/test/common/sample_data/lift/liftTwoEntriesCorrectedV0_13.lift b/sample_data/lift/liftTwoEntriesCorrectedV0_13.lift similarity index 100% rename from test/common/sample_data/lift/liftTwoEntriesCorrectedV0_13.lift rename to sample_data/lift/liftTwoEntriesCorrectedV0_13.lift diff --git a/test/common/sample_data/lift/liftTwoEntriesModifiedV0_13.lift b/sample_data/lift/liftTwoEntriesModifiedV0_13.lift similarity index 100% rename from test/common/sample_data/lift/liftTwoEntriesModifiedV0_13.lift rename to sample_data/lift/liftTwoEntriesModifiedV0_13.lift diff --git a/test/common/sample_data/lift/liftTwoEntriesV0_13.lift b/sample_data/lift/liftTwoEntriesV0_13.lift similarity index 100% rename from test/common/sample_data/lift/liftTwoEntriesV0_13.lift rename to sample_data/lift/liftTwoEntriesV0_13.lift diff --git a/test/common/sample_data/lift/liftTwoEntriesWithSpanV0_13.lift b/sample_data/lift/liftTwoEntriesWithSpanV0_13.lift similarity index 100% rename from test/common/sample_data/lift/liftTwoEntriesWithSpanV0_13.lift rename to sample_data/lift/liftTwoEntriesWithSpanV0_13.lift diff --git a/test/common/sample_data/lift/lift_13.pdf b/sample_data/lift/lift_13.pdf similarity index 100% rename from test/common/sample_data/lift/lift_13.pdf rename to sample_data/lift/lift_13.pdf diff --git a/test/common/sample_data/lift/sampledf.lift b/sample_data/lift/sampledf.lift similarity index 100% rename from test/common/sample_data/lift/sampledf.lift rename to sample_data/lift/sampledf.lift diff --git a/test/common/sample_data/lift/tha-food-small.lift b/sample_data/lift/tha-food-small.lift similarity index 100% rename from test/common/sample_data/lift/tha-food-small.lift rename to sample_data/lift/tha-food-small.lift diff --git a/test/common/sample_data/lift/tha-food.WeSayConfig b/sample_data/lift/tha-food.WeSayConfig similarity index 100% rename from test/common/sample_data/lift/tha-food.WeSayConfig rename to sample_data/lift/tha-food.WeSayConfig diff --git a/test/common/sample_data/lift/tha-food.lift b/sample_data/lift/tha-food.lift similarity index 100% rename from test/common/sample_data/lift/tha-food.lift rename to sample_data/lift/tha-food.lift diff --git a/test/common/sample_data/semdom/SemDom_en_sample.xml b/sample_data/semdom/SemDom_en_sample.xml similarity index 100% rename from test/common/sample_data/semdom/SemDom_en_sample.xml rename to sample_data/semdom/SemDom_en_sample.xml diff --git a/test/app/allspecs/after-each.e2e-spec.ts b/test/app/allspecs/after-each.e2e-spec.ts deleted file mode 100644 index cabba7e567..0000000000 --- a/test/app/allspecs/after-each.e2e-spec.ts +++ /dev/null @@ -1,44 +0,0 @@ -import 'jasmine'; -import {browser} from 'protractor'; - -import {SfAppFrame} from '../bellows/shared/app.frame'; -import {PageBody} from '../bellows/shared/page-body.element'; -import {Utils} from '../bellows/shared/utils'; - -afterEach(() => { - const appFrame = new SfAppFrame(); - const body = new PageBody(); - - appFrame.errorMessage.isPresent().then(isPresent => { - if (isPresent) { - appFrame.errorMessage.getText().then(message => { - if (message.includes('Oh. Exception')) { - message = 'PHP API error on this page: ' + message; - expect(message).toEqual(''); // fail the test - } - }); - } - }); - - body.phpError.isPresent().then(isPresent => { - if (isPresent) { - body.phpError.getText().then(message => { - message = 'PHP Error present on this page:' + message; - expect(message).toEqual(''); // fail the test - }); - } - }); - - // output JS console errors and fail tests - browser.manage().logs().get('browser').then(browserLogs => { - for (const browserLog of browserLogs) { - let text = browserLog.message; - if (Utils.isMessageToIgnore(browserLog)) { - return; - } - - text = '\n\nBrowser Console JS Error: \n' + text + '\n\n'; - expect(text).toEqual(''); // fail the test - } - }); -}); diff --git a/test/app/bellows/bellows-traversal.e2e-spec.ts b/test/app/bellows/bellows-traversal.e2e-spec.ts deleted file mode 100644 index f0cf566717..0000000000 --- a/test/app/bellows/bellows-traversal.e2e-spec.ts +++ /dev/null @@ -1,87 +0,0 @@ -import 'jasmine'; - -import {SfActivityPage} from './shared/activity.page'; -import {BellowsChangePasswordPage} from './shared/change-password.page'; -import {BellowsForgotPasswordPage} from './shared/forgot-password.page'; -import {BellowsLoginPage} from './shared/login.page'; -import {ProjectsPage} from './shared/projects.page'; -import {BellowsResetPasswordPage} from './shared/reset-password.page'; -import {SignupPage} from './shared/signup.page'; -import {SiteAdminPage} from './shared/site-admin.page'; -import {SfUserProfilePage} from './shared/user-profile.page'; - -describe('Bellows E2E Page Traversal', () => { - const constants = require('../testConstants.json'); - const activityPage = new SfActivityPage(); - const changePasswordPage = new BellowsChangePasswordPage(); - const forgotPasswordPage = new BellowsForgotPasswordPage(); - const loginPage = new BellowsLoginPage(); - const projectsPage = new ProjectsPage(); - const resetPasswordPage = new BellowsResetPasswordPage(); - const signupPage = new SignupPage(); - const siteAdminPage = new SiteAdminPage(); - const userProfilePage = new SfUserProfilePage(); - - it('Explore signup page' , async () => { - await BellowsLoginPage.logout(); - await SignupPage.get(); - await signupPage.emailInput.clear(); - await signupPage.nameInput.clear(); - await signupPage.passwordInput.clear(); - await signupPage.captcha.blueSquareButton.click(); - await signupPage.captcha.yellowCircleButton.click(); - await signupPage.captcha.redTriangleButton.click(); - }); - - it('Explore forgot password page', async () => { - await BellowsForgotPasswordPage.get(); - await forgotPasswordPage.usernameInput.clear(); - await forgotPasswordPage.submitButton.click(); - }); - - it('Explore reset password page', async () => { - await BellowsResetPasswordPage.get(constants.resetPasswordKey); - await resetPasswordPage.passwordInput.clear(); - await resetPasswordPage.confirmPasswordInput.clear(); - await resetPasswordPage.resetButton.click(); - }); - - it('Explore login page', async () => { - await BellowsLoginPage.get(); - await loginPage.loginAsAdmin(); - }); - - it('Explore change password page', async () => { - await changePasswordPage.get(); - await changePasswordPage.password.clear(); - await changePasswordPage.confirm.clear(); - await changePasswordPage.submitButton.click(); - }); - - it('Explore activity page', async () => { - await activityPage.get(); - await activityPage.activitiesList.count(); - }); - - it('Explore project page', async () => { - await projectsPage.get(); - await projectsPage.projectsList.count(); - await projectsPage.projectNames.count(); - await projectsPage.createBtn.click(); - }); - - it('Explore site admin page', async () => { - await siteAdminPage.get(); - await siteAdminPage.tabs.archivedProjects.click(); - await siteAdminPage.archivedProjectsTab.republishButton.click(); - await siteAdminPage.archivedProjectsTab.deleteButton.click(); - await siteAdminPage.archivedProjectsTab.projectsList.count(); - }); - - it('Explore user profile page', async () => { - await userProfilePage.get(); - await userProfilePage.activitiesList.count(); - await userProfilePage.tabs.aboutMe.click(); - await userProfilePage.tabs.myAccount.click(); - }); -}); diff --git a/test/app/bellows/change-password.e2e-spec.ts b/test/app/bellows/change-password.e2e-spec.ts deleted file mode 100644 index 5b94b37de6..0000000000 --- a/test/app/bellows/change-password.e2e-spec.ts +++ /dev/null @@ -1,69 +0,0 @@ -import {browser, ExpectedConditions} from 'protractor'; - -import { BellowsChangePasswordPage } from './shared/change-password.page'; -import { BellowsLoginPage } from './shared/login.page'; -import { PageHeader } from './shared/page-header.element'; - -describe('Bellows E2E Change Password app', () => { - const constants = require('../testConstants.json'); - const loginPage = new BellowsLoginPage(); - const header = new PageHeader(); - const changePasswordPage = new BellowsChangePasswordPage(); - const newPassword = '12345678'; - - it('setup: login as user, go to change password page', async () => { - await loginPage.loginAsUser(); - await changePasswordPage.get(); - }); - - it('refuses to allow form submission if the confirm input does not match', async () => { - await changePasswordPage.password.sendKeys(newPassword); - await changePasswordPage.confirm.sendKeys('blah12345'); - expect(await changePasswordPage.submitButton.isEnabled()).toBeFalsy(); - await changePasswordPage.password.clear(); - await changePasswordPage.confirm.clear(); - }); - - it('allows form submission if the confirm input matches', async () => { - await changePasswordPage.password.sendKeys(newPassword); - await changePasswordPage.confirm.sendKeys(newPassword); - expect(await changePasswordPage.submitButton.isEnabled()).toBeTruthy(); - await changePasswordPage.password.clear(); - await changePasswordPage.confirm.clear(); - }); - - /* cant test this yet because I don't know how to test for HTML 5 form validation - cjh 2014-06 - it('should not allow a password less than 7 characters', function() { - var shortPassword = '12345'; - changePasswordPage.password.sendKeys(shortPassword); - changePasswordPage.confirm.sendKeys(shortPassword); - expect(changePasswordPage.submitButton.isEnabled()).toBe(false); - changePasswordPage.password.clear(); - changePasswordPage.confirm.clear(); - }); - */ - - it('can successfully changes user\'s password after form submission', async () => { - await changePasswordPage.password.sendKeys(newPassword); - await changePasswordPage.confirm.sendKeys(newPassword); - await browser.wait(ExpectedConditions.visibilityOf(changePasswordPage.passwordMatchImage), constants.conditionTimeout); - await browser.wait(ExpectedConditions.elementToBeClickable(changePasswordPage.submitButton), constants.conditionTimeout); - await changePasswordPage.submitButton.click(); - expect(await changePasswordPage.noticeList.count()).toBe(1); - expect(await changePasswordPage.noticeList.first().getText()).toContain('Password updated'); - await BellowsLoginPage.logout(); - - await loginPage.login(constants.memberUsername, newPassword); - await browser.wait(ExpectedConditions.visibilityOf(header.myProjects.button), constants.conditionTimeout); - expect(await header.myProjects.button.isDisplayed()).toBe(true); - - // reset password back to original - await changePasswordPage.get(); - await changePasswordPage.password.sendKeys(constants.memberPassword); - await changePasswordPage.confirm.sendKeys(constants.memberPassword); - await browser.wait(ExpectedConditions.visibilityOf(changePasswordPage.passwordMatchImage), constants.conditionTimeout); - await browser.wait(ExpectedConditions.elementToBeClickable(changePasswordPage.submitButton), constants.conditionTimeout); - await changePasswordPage.submitButton.click(); - }); - -}); diff --git a/test/app/bellows/project-settings.e2e-spec.ts b/test/app/bellows/project-settings.e2e-spec.ts deleted file mode 100644 index 83134f4942..0000000000 --- a/test/app/bellows/project-settings.e2e-spec.ts +++ /dev/null @@ -1,151 +0,0 @@ -import { browser, ExpectedConditions } from 'protractor'; - -import {BellowsLoginPage} from './shared/login.page'; -import {BellowsProjectSettingsPage} from './shared/project-settings.page'; -import {ProjectsPage} from './shared/projects.page'; -import {Utils} from './shared/utils'; - -describe('Bellows E2E Project Settings app', () => { - const constants = require('../testConstants.json'); - const loginPage = new BellowsLoginPage(); - const projectsPage = new ProjectsPage(); - const settingsPage = new BellowsProjectSettingsPage(); - - it('Normal user cannot access projectSettings to a project of which the user is a member', async () => { - await loginPage.loginAsMember(); - await projectsPage.get(); - await projectsPage.clickOnProject(constants.testProjectName); - expect(await settingsPage.settingsMenuLink.isPresent()).toBe(false); - }); - - it('System Admin can manage project', async () => { - await loginPage.loginAsAdmin(); - await settingsPage.get(constants.testProjectName); - expect(await settingsPage.noticeList.count()).toBe(0); - - // Archive tab currently disabled - /* - managementPage.tabs.archive.click(); - expect(managementPage.archiveTab.archiveButton.isDisplayed()).toBe(true); - expect(managementPage.archiveTab.archiveButton.isEnabled()).toBe(true); - */ - await browser.wait(ExpectedConditions.visibilityOf(settingsPage.tabs.remove), constants.conditionTimeout); - // await browser.actions().mouseMove(settingsPage.tabs.remove).click().perform(); - await settingsPage.tabs.remove.click(); - // await browser.wait(ExpectedConditions.visibilityOf(settingsPage.deleteTab.deleteButton), constants.conditionTimeout); - expect(await settingsPage.deleteTab.deleteButton.isDisplayed()).toBe(true); - expect(await settingsPage.deleteTab.deleteButton.isEnabled()).toBe(false); - }); - - it('confirm Manager is not owner of test project', async () => { - await loginPage.loginAsManager(); - await settingsPage.get(constants.testProjectName); - await settingsPage.tabs.project.click(); - expect(await settingsPage.projectTab.projectOwner.isDisplayed()).toBe(true); - expect(await settingsPage.projectTab.projectOwner.getText()) - .not.toContain(constants.managerUsername); - }); - - // Archive tab is a disabled/hidden feature - /* - xit('Manager cannot view archive tab if not owner', function () { - expect(settingsPage.tabs.archive.isPresent()).toBe(false); - }); - */ - - it('Manager cannot view delete tab if not owner', async () => { - expect(await settingsPage.tabs.remove.isPresent()).toBe(false); - }); - - it('confirm Manager is owner of fourth project', async () => { - await loginPage.loginAsManager(); - await settingsPage.get(constants.fourthProjectName); - await settingsPage.tabs.project.click(); - expect(await settingsPage.projectTab.projectOwner.isDisplayed()).toBe(true); - expect(await settingsPage.projectTab.projectOwner.getText()).toContain(constants.managerUsername); - }); - - // For Jamaican Psalms, only system admins can delete projects. - // Project Manager is an ordinary user, so this test is ignored for Jamaican Psalms - it('Manager can delete if owner', async () => { - if (!browser.baseUrl.startsWith('http://jamaicanpsalms') && !browser.baseUrl.startsWith('https://jamaicanpsalms')) { - await loginPage.loginAsManager(); - await projectsPage.get(); - expect(await projectsPage.projectsList.count()).toBe(4); - await settingsPage.get(constants.fourthProjectName); - expect(await settingsPage.noticeList.count()).toBe(0); - await settingsPage.tabs.remove.click(); - await browser.wait(ExpectedConditions.visibilityOf(settingsPage.deleteTab.deleteButton), constants.conditionTimeout); - expect(await settingsPage.deleteTab.deleteButton.isDisplayed()).toBe(true); - expect(await settingsPage.deleteTab.deleteButton.isEnabled()).toBe(false); - await settingsPage.deleteTab.deleteBoxText.sendKeys('DELETE'); - expect(await settingsPage.deleteTab.deleteButton.isEnabled()).toBe(true); - await settingsPage.deleteTab.deleteButton.click(); - await Utils.clickModalButton('Delete'); - // await browser.wait(() => false, constants.conditionTimeout * 10); - await projectsPage.get(); - expect(await projectsPage.projectsList.count()).toBe(3); - } - }); - - // Since Archive tab is now disabled, ignoring Archive / re-publish tests - /* - xit('Manager can archive if owner', function () { - loginPage.loginAsManager(); - settingsPage.get(constants.testProjectName); - expect(settingsPage.noticeList.count()).toBe(0); - settingsPage.tabs.archive.click(); - expect(settingsPage.archiveTab.archiveButton.isDisplayed()).toBe(true); - expect(settingsPage.archiveTab.archiveButton.isEnabled()).toBe(true); - settingsPage.archiveTab.archiveButton.click(); - util.clickModalButton('Archive'); - expect(projectsPage.projectsList.count()).toBe(2); - }).pend('Archive tab is currently disabled'); - - xit('System Admin can re-publish project', function () { - loginPage.loginAsAdmin(); - siteAdminPage.get(); - siteAdminPage.tabs.archivedProjects.click(); - expect(siteAdminPage.tabs.archivedProjects.republishButton.isDisplayed()).toBe(true); - expect(siteAdminPage.tabs.archivedProjects.republishButton.isEnabled()).toBe(false); - expect(siteAdminPage.tabs.archivedProjects.deleteButton.isDisplayed()).toBe(true); - expect(siteAdminPage.tabs.archivedProjects.deleteButton.isEnabled()).toBe(false); - expect(siteAdminPage.tabs.archivedProjects.projectsList.count()).toBe(1); - siteAdminPage.tabs.archivedProjects.setCheckbox(0, true); - expect(siteAdminPage.tabs.archivedProjects.republishButton.isEnabled()).toBe(true); - expect(siteAdminPage.tabs.archivedProjects.deleteButton.isEnabled()).toBe(true); - siteAdminPage.tabs.archivedProjects.republishButton.click(); - projectsPage.get(); - expect(projectsPage.projectsList.count()).toBe(3); - }).pend('Archive tab is currently disabled'); - - xit('System Admin can archive', function () { - loginPage.loginAsAdmin(); - settingsPage.get(constants.testProjectName); - expect(settingsPage.noticeList.count()).toBe(0); - settingsPage.tabs.archive.click(); - expect(settingsPage.archiveTab.archiveButton.isDisplayed()).toBe(true); - expect(settingsPage.archiveTab.archiveButton.isEnabled()).toBe(true); - settingsPage.archiveTab.archiveButton.click(); - util.clickModalButton('Archive'); - expect(projectsPage.projectsList.count()).toBe(2); - }).pend('Archive tab is currently disabled'); - - xit('System Admin can re-publish project', function () { - siteAdminPage.get(); - siteAdminPage.tabs.archivedProjects.click(); - expect(siteAdminPage.tabs.archivedProjects.republishButton.isDisplayed()).toBe(true); - expect(siteAdminPage.tabs.archivedProjects.republishButton.isEnabled()).toBe(false); - expect(siteAdminPage.tabs.archivedProjects.deleteButton.isDisplayed()).toBe(true); - expect(siteAdminPage.tabs.archivedProjects.deleteButton.isEnabled()).toBe(false); - expect(siteAdminPage.tabs.archivedProjects.projectsList.count()).toBe(1); - siteAdminPage.tabs.archivedProjects.setCheckbox(0, true); - expect(siteAdminPage.tabs.archivedProjects.republishButton.isEnabled()).toBe(true); - expect(siteAdminPage.tabs.archivedProjects.deleteButton.isEnabled()).toBe(true); - siteAdminPage.tabs.archivedProjects.republishButton.click(); - projectsPage.get(); - expect(projectsPage.projectsList.count()).toBe(3); - }).pend('Archive tab is currently disabled'); - */ - -}); diff --git a/test/app/bellows/projects.e2e-spec.ts b/test/app/bellows/projects.e2e-spec.ts deleted file mode 100644 index 040b7049a5..0000000000 --- a/test/app/bellows/projects.e2e-spec.ts +++ /dev/null @@ -1,133 +0,0 @@ -import {browser, by, ExpectedConditions, element} from 'protractor'; -import {ElementFinder} from 'protractor/built/element'; - -import {EditorPage} from '../languageforge/lexicon/shared/editor.page'; -import {BellowsLoginPage} from './shared/login.page'; -import {ProjectsPage} from './shared/projects.page'; - -describe('Bellows E2E Projects List app', () => { - const constants = require('../testConstants.json'); - const loginPage = new BellowsLoginPage(); - const projectsPage = new ProjectsPage(); - const editorPage = new EditorPage(); - const projectNameLabel = element(by.className('page-name ng-binding')); - - describe('for Normal User', () => { - - it('should list the project of which the user is a member', async () => { - await loginPage.loginAsMember(); - await projectsPage.get(); - expect(await projectsPage.projectNames.get(0).getText()).toBe(constants.testProjectName); - }); - - it('should not list projects the user is not a member of', async () => { - await projectsPage.get(); - expect(await projectsPage.projectsList.count()).toBe(1); - }); - - it('can list two projects of which the user is a member', async () => { - await loginPage.loginAsAdmin(); - await projectsPage.get(); - await browser.wait(ExpectedConditions.visibilityOf(projectsPage.createBtn), constants.conditionTimeout); - await projectsPage.addMemberToProject(constants.otherProjectName, constants.memberName); - await loginPage.loginAsMember(); - await projectsPage.get(); - await browser.wait(ExpectedConditions.visibilityOf(projectsPage.createBtn), constants.conditionTimeout); - expect(await projectsPage.projectsList.count()).toBe(2); - }); - }); - - // Two helper functions to avoid duplicating the same checks in admin test below - const shouldProjectBeLinked = async (projectName: string, projectRow: ElementFinder, bool: boolean) => { - expect(await projectRow.element(by.cssContainingText('a', projectName)).isDisplayed()).toBe(bool); - }; - - const shouldProjectHaveButtons = async (projectRow: ElementFinder, bool: boolean) => { - const addAsTechSupportBtn = projectRow.element(by.id('techSupportButton')); - expect(await addAsTechSupportBtn.isDisplayed()).toBe(bool); - }; - - describe('for System Admin User', () => { - - it('should list all projects', async () => { - await loginPage.loginAsAdmin(); - await projectsPage.get(); - expect(await projectsPage.projectsList.count()).toBeGreaterThan(0); - - // Check that the test project is around - return projectsPage.findProject(constants.testProjectName).then((projectRow: ElementFinder) => { - return shouldProjectBeLinked(constants.testProjectName, projectRow, true); - }); - }); - - it('should show add and delete buttons', async () => { - // projectsPage.createBtn.getOuterHtml().then(console.log); - expect(await projectsPage.createBtn.isDisplayed()).toBeTruthy(); - }); - - it('should allow the admin to add themselves to the project as member or manager', async () => { - - // First remove the admin from the project (must be a project admin is not the owner of) - await loginPage.loginAsManager(); - await projectsPage.get(); - - // The admin should not see "Add myself to project" buttons when he's already a project member - // or manager, and the project name should be a clickable link - await projectsPage.findProject(constants.otherProjectName).then(async (projectRow: ElementFinder) => { - await shouldProjectBeLinked(constants.otherProjectName, projectRow, true); - return shouldProjectHaveButtons(projectRow, false); - }); - - await projectsPage.removeUserFromProject(constants.otherProjectName, constants.adminUsername); - await loginPage.loginAsAdmin(); - await projectsPage.get(); - - // Now the admin should have "Add myself to project" buttons - // And the project name should NOT be a clickable link - await projectsPage.findProject(constants.otherProjectName).then(async (projectRow: ElementFinder) => { - await shouldProjectBeLinked(constants.otherProjectName, projectRow, false); - await shouldProjectHaveButtons(projectRow, true); - - // Now add the admin back to the project - return projectRow.element(by.id('techSupportButton')).click(); - }); - - // And the buttons should go away after one of them is clicked - return projectsPage.findProject(constants.otherProjectName).then(async (projectRow: ElementFinder) => { - await shouldProjectBeLinked(constants.otherProjectName, projectRow, true); - return shouldProjectHaveButtons(projectRow, false); - }); - }); - - }); - - describe('Lexicon E2E Project Access', () => { - - it('Admin added to project when accessing without membership', async () => { - /* This test passes on my local machine. It's a valid test. However it fails on GHA for an unknown reason. - I am going to comment out this test so that it is still present to be converted to Cyprus E2E when that happens - await loginPage.loginAsManager(); - const url = await browser.getCurrentUrl(); - const projectName = await projectNameLabel.getText(); - await projectsPage.get(); - await browser.wait(ExpectedConditions.visibilityOf(projectsPage.createBtn), constants.conditionTimeout); - await projectsPage.removeUserFromProject(projectName, constants.adminUsername); - await loginPage.loginAsAdmin(); - await browser.get(url); - await browser.wait(ExpectedConditions.visibilityOf(editorPage.editDiv), constants.conditionTimeout); - expect(await editorPage.editDiv.isPresent()).toBe(true); - */ - }); - - it('User redirected to projects app when accessing without membership', async () => { - await loginPage.loginAsManager(); - const url = await browser.getCurrentUrl(); - await loginPage.loginAsSecondUser(); - await browser.get(url); - await browser.wait(ExpectedConditions.visibilityOf(projectsPage.createBtn), constants.conditionTimeout); - expect(await projectsPage.createBtn.isPresent()).toBe(true); - }); - - }); - -}); diff --git a/test/app/bellows/reset-forgotten-password.e2e-spec.ts b/test/app/bellows/reset-forgotten-password.e2e-spec.ts deleted file mode 100644 index 068c61d25b..0000000000 --- a/test/app/bellows/reset-forgotten-password.e2e-spec.ts +++ /dev/null @@ -1,129 +0,0 @@ -import {browser, ExpectedConditions} from 'protractor'; - -import {BellowsForgotPasswordPage} from './shared/forgot-password.page'; -import {BellowsLoginPage} from './shared/login.page'; -import {PageHeader} from './shared/page-header.element'; -import {BellowsResetPasswordPage} from './shared/reset-password.page'; - -describe('Bellows E2E Reset Forgotten Password app', () => { - const constants = require('./../testConstants.json'); - const header = new PageHeader(); - const loginPage = new BellowsLoginPage(); - const resetPasswordPage = new BellowsResetPasswordPage(); - const forgotPasswordPage = new BellowsForgotPasswordPage(); - - it('with expired reset key routes to login with warning', async () => { - await BellowsLoginPage.logout(); - await BellowsResetPasswordPage.get(constants.expiredPasswordKey); - await browser.wait(ExpectedConditions.visibilityOf(loginPage.errors.get(0)), constants.conditionTimeout); - expect(await loginPage.username.isDisplayed()).toBe(true); - expect(await loginPage.infoMessages.count()).toBe(0); - expect(await loginPage.errors.count()).toBe(1); - expect(await loginPage.errors.first().getText()).toContain('expired'); - - // clear errors so that afterEach appFrame error check doesn't fail, see project-settings.e2e-spec.js - await browser.refresh(); - expect(await loginPage.errors.count()).toBe(0); - }); - - describe('for Forgot Password request', () => { - - it('can navigate to request page', async () => { - await loginPage.forgotPasswordLink.click(); - await browser.wait(ExpectedConditions.stalenessOf(loginPage.forgotPasswordLink), constants.conditionTimeout); - await browser.wait(ExpectedConditions.visibilityOf(forgotPasswordPage.usernameInput), constants.conditionTimeout); - expect(await forgotPasswordPage.usernameInput.isDisplayed()).toBe(true); - }); - - it('cannot request for non-existent user', async () => { - await BellowsForgotPasswordPage.get(); - expect(await forgotPasswordPage.infoMessages.count()).toBe(0); - expect(await forgotPasswordPage.errors.count()).toBe(0); - await forgotPasswordPage.usernameInput.sendKeys(constants.unusedUsername); - await forgotPasswordPage.submitButton.click(); - await browser.wait(ExpectedConditions.visibilityOf(forgotPasswordPage.errors.get(0)), constants.conditionTimeout); - expect(await forgotPasswordPage.errors.count()).toBe(1); - expect(await forgotPasswordPage.errors.first().getText()).toContain('User not found'); - forgotPasswordPage.usernameInput.clear(); - - // clear errors so that afterEach appFrame error check doesn't fail, see project-settings.e2e-spec.js - await browser.refresh(); - expect(await forgotPasswordPage.errors.count()).toBe(0); - }); - - it('can submit request', async () => { - await forgotPasswordPage.usernameInput.sendKeys(constants.expiredUsername); - await forgotPasswordPage.submitButton.click(); - expect(await forgotPasswordPage.errors.count()).toBe(0); - browser.wait(ExpectedConditions.stalenessOf(resetPasswordPage.confirmPasswordInput), constants.conditionTimeout); - browser.wait(ExpectedConditions.visibilityOf(loginPage.infoMessages.get(0)), constants.conditionTimeout); - expect(await loginPage.username.isDisplayed()).toBe(true); - expect(await loginPage.errors.count()).toBe(0); - expect(await loginPage.infoMessages.count()).toBe(1); - expect(await loginPage.infoMessages.first().getText()).toContain('email sent'); - }); - - }); - - describe('for Reset Password', () => { - - it('with valid reset key routes reset page', async () => { - await BellowsResetPasswordPage.get(constants.resetPasswordKey); - await expect(await resetPasswordPage.confirmPasswordInput.isDisplayed()).toBe(true); - await expect(await resetPasswordPage.errors.count()).toBe(0); - await expect(await loginPage.infoMessages.count()).toBe(0); - }); - - it('refuses to allow form submission if the confirm input does not match', async () => { - await resetPasswordPage.passwordInput.sendKeys(constants.passwordValid); - await resetPasswordPage.confirmPasswordInput.sendKeys(constants.passwordTooShort); - expect(await resetPasswordPage.resetButton.isEnabled()).toBe(false); - await resetPasswordPage.passwordInput.clear(); - await resetPasswordPage.confirmPasswordInput.clear(); - }); - - it('allows form submission if the confirm input matches', async () => { - await resetPasswordPage.passwordInput.sendKeys(constants.passwordValid); - await resetPasswordPage.confirmPasswordInput.sendKeys(constants.passwordValid); - expect(await resetPasswordPage.resetButton.isEnabled()).toBe(true); - await resetPasswordPage.passwordInput.clear(); - await resetPasswordPage.confirmPasswordInput.clear(); - }); - - it('should not allow a password less than 7 characters', async () => { - await resetPasswordPage.passwordInput.sendKeys(constants.passwordTooShort); - await resetPasswordPage.confirmPasswordInput.sendKeys(constants.passwordTooShort); - expect(await resetPasswordPage.resetButton.isEnabled()).toBe(false); - await resetPasswordPage.passwordInput.clear(); - await resetPasswordPage.confirmPasswordInput.clear(); - }); - - it('successfully change user\'s password', async () => { - await BellowsResetPasswordPage.get(constants.resetPasswordKey); - await resetPasswordPage.passwordInput.sendKeys(constants.resetPassword); - await resetPasswordPage.confirmPasswordInput.sendKeys(constants.resetPassword); - await resetPasswordPage.resetButton.click(); - - // browser.wait(ExpectedConditions.stalenessOf(resetPasswordPage.confirmPasswordInput), - // constants.conditionTimeout); - // 'stalenessOf' occasionally failed with - // WebDriverError: javascript error: document unloaded while waiting for result - await browser.sleep(100); - await browser.wait(ExpectedConditions.visibilityOf(loginPage.infoMessages.get(0)), constants.conditionTimeout); - expect(await loginPage.username.isDisplayed()).toBe(true); - expect(await loginPage.form.isPresent()).toBe(true); - expect(await loginPage.infoMessages.count()).toBe(1); - expect(await loginPage.infoMessages.first().getText()).toContain('password has been reset'); - expect(await loginPage.errors.count()).toBe(0); - }); - - it('successfully login after password change', async () => { - await BellowsLoginPage.get(); - await loginPage.login(constants.resetUsername, constants.resetPassword); - expect(await header.loginButton.isPresent()).toBe(false); - expect(await header.myProjects.button.isDisplayed()).toBe(true); - }); - - }); - -}); diff --git a/test/app/bellows/shared/activity.page.ts b/test/app/bellows/shared/activity.page.ts deleted file mode 100644 index 79f7c58883..0000000000 --- a/test/app/bellows/shared/activity.page.ts +++ /dev/null @@ -1,58 +0,0 @@ -import {browser, by, element} from 'protractor'; -import {ElementFinder} from 'protractor/built/element'; - -// This object handles the activity page and provides methods to access items in the activity list -export class SfActivityPage { - activityURL = '/app/activity'; - activitiesList = element.all(by.className('activity-content')); - activityGroups = element.all(by.className('activity-user-group')); - filterByUser = element(by.id('filter_by_user')); - - // Navigate to the Activity page - get() { - return browser.get(browser.baseUrl + this.activityURL); - } - - clickOnAllActivity() { - return this.filterByUser.element(by.css('option:nth-child(1)')).click(); - } - - clickOnShowOnlyMyActivity() { - return this.filterByUser.element(by.css('option:nth-child(2)')).click(); - } - - // Returns the number of items in the activity list - //noinspection JSUnusedGlobalSymbols - getLength() { - return this.activitiesList.count(); - } - - // Returns the text in the activity list for a specified index - getActivityText(index: number) { - return this.activitiesList.get(index).getText(); - } - - getAllActivityTexts() { - return this.activitiesList.map((elem: ElementFinder) => elem.getText()); - } - - getActivityGroup(index: number) { - return SfActivityPage.getPartsOfActivity(this.activityGroups.get(index)); - } - - // Prints the entire activity list - //noinspection JSUnusedGlobalSymbols - printActivitiesNames() { - return (this.activitiesList).each((names: ElementFinder) => { - names.getText().then(console.log); - }); - } - - static getPartsOfActivity(div: ElementFinder) { - return { - activity: div, - user: div.element(by.className('activity-username')).getText(), - activities: div.all(by.className('activity-content')).map((elem: ElementFinder) => elem.getText()) - }; - } -} diff --git a/test/app/bellows/shared/app.frame.ts b/test/app/bellows/shared/app.frame.ts deleted file mode 100644 index 7e28aeb0d3..0000000000 --- a/test/app/bellows/shared/app.frame.ts +++ /dev/null @@ -1,24 +0,0 @@ -import {by, element} from 'protractor'; - -export class SfAppFrame { - - // TODO: this will likely change when we refactor the display of notifications - cjh 2014-06 - message = { - success: element(by.css('.alert-success')), - info: element(by.css('.alert-info')), - warn: element(by.css('.alert-warning')), - error: element.all(by.css('.alert-danger')).first() - }; - - // Alternate names for the above - successMessage = this.message.success; - infoMessage = this.message.info; - warnMessage = this.message.warn; - errorMessage = this.message.error; - - checkMsg(expected: string, msgType: string) { - msgType = msgType || 'success'; - expect(this.message[msgType].getText()).toMatch(expected); - } - -} diff --git a/test/app/bellows/shared/change-password.page.ts b/test/app/bellows/shared/change-password.page.ts deleted file mode 100644 index 11946bf99a..0000000000 --- a/test/app/bellows/shared/change-password.page.ts +++ /dev/null @@ -1,17 +0,0 @@ -import {browser, by, element, ExpectedConditions} from 'protractor'; - -export class BellowsChangePasswordPage { - conditionTimeout: number = 12000; - - // TODO: this will likely change when we refactor the display of notifications - cjh 2014-06 - async get() { - await browser.get(browser.baseUrl + '/app/changepassword'); - return browser.wait(ExpectedConditions.visibilityOf(this.password), this.conditionTimeout); - } - - password = element(by.id('change-password-input')); - confirm = element(by.id('change-password-confirm-input')); - passwordMatchImage = element(by.id('change-password-match')); - submitButton = element(by.id('change-password-submit-button')); - noticeList = element.all(by.repeater('notice in $ctrl.notices()')); -} diff --git a/test/app/bellows/shared/forgot-password.page.ts b/test/app/bellows/shared/forgot-password.page.ts deleted file mode 100644 index a3da37615b..0000000000 --- a/test/app/bellows/shared/forgot-password.page.ts +++ /dev/null @@ -1,14 +0,0 @@ -import {browser, by, element} from 'protractor'; - -export class BellowsForgotPasswordPage { - static get() { - return browser.get(browser.baseUrl + '/auth/forgot_password'); - } - - form = element(by.id('forgot-password-form')); - // infoMessage and errors are dynamic elements, so class name locators seem to be the best option - infoMessages = element.all(by.className('alert-info')); - errors = element.all(by.className('alert-danger')); - usernameInput = element(by.id('username')); - submitButton = element(by.id('forgot-password-submit-btn')); -} diff --git a/test/app/bellows/shared/login.page.ts b/test/app/bellows/shared/login.page.ts deleted file mode 100644 index acb38ef71e..0000000000 --- a/test/app/bellows/shared/login.page.ts +++ /dev/null @@ -1,58 +0,0 @@ -import {browser, by, element, ExpectedConditions} from 'protractor'; - -export class BellowsLoginPage { - private readonly constants = require('../../testConstants'); - - static get() { - return browser.get(browser.baseUrl + '/auth/login'); - } - - form = element(by.id('login-loginForm')); - infoMessages = element.all(by.className('alert-info')); - errors = element.all(by.css('.alert-danger')); - username = element(by.id('username')); - password = element(by.id('password')); - forgotPasswordLink = element(by.id('forgot_password')); - submit = element(by.id('login-submit')); - lexiconLoading = element(by.id('loadingMessage')); - - async login(username: string, password: string) { - await browser.get(browser.baseUrl + '/auth/logout'); - - await BellowsLoginPage.get(); - await this.username.sendKeys(username); - await this.password.sendKeys(password); - await this.submit.click(); - await browser.wait(ExpectedConditions.not(ExpectedConditions.urlContains('/auth/login')), - this.constants.conditionTimeout); - return browser.wait(ExpectedConditions.invisibilityOf(this.lexiconLoading), this.constants.conditionTimeout); - } - - loginAsAdmin() { - return this.login(this.constants.adminEmail, this.constants.adminPassword); - } - - loginAsManager() { - return this.login(this.constants.managerEmail, this.constants.managerPassword); - } - - loginAsUser() { - return this.login(this.constants.memberEmail, this.constants.memberPassword); - } - - loginAsMember = this.loginAsUser; - - loginAsSecondUser() { - return this.login(this.constants.member2Email, this.constants.member2Password); - } - - loginAsSecondMember = this.loginAsSecondUser; - - loginAsObserver() { - return this.login(this.constants.observerEmail, this.constants.observerPassword); - } - - static logout() { - return browser.get(browser.baseUrl + '/auth/logout'); - } -} diff --git a/test/app/bellows/shared/mock-upload.element.ts b/test/app/bellows/shared/mock-upload.element.ts deleted file mode 100644 index 1887dab133..0000000000 --- a/test/app/bellows/shared/mock-upload.element.ts +++ /dev/null @@ -1,8 +0,0 @@ -import {by, element} from 'protractor'; - -export class MockUploadElement { - enableButton = element(by.id('showMockUploadButton')); - fileNameInput = element(by.id('mockFileName')); - fileSizeInput = element(by.id('mockFileSize')); - uploadButton = element(by.id('mockUploadButton')); -} diff --git a/test/app/bellows/shared/page-body.element.ts b/test/app/bellows/shared/page-body.element.ts deleted file mode 100644 index 8faff05cbf..0000000000 --- a/test/app/bellows/shared/page-body.element.ts +++ /dev/null @@ -1,5 +0,0 @@ -import {by, element} from 'protractor'; - -export class PageBody { - phpError = element(by.xpath('//*[contains(.,\'A PHP Error was encountered\')]')); -} diff --git a/test/app/bellows/shared/page-header.element.ts b/test/app/bellows/shared/page-header.element.ts deleted file mode 100644 index 872162430a..0000000000 --- a/test/app/bellows/shared/page-header.element.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {by, element} from 'protractor'; - -export class PageHeader { - myProjects = { - button: element(by.id('myProjectDropdownButton')), - links: element(by.id('myProjectDropdownMenu')).all(by.className('dropdown-item')) - }; - - loginButton = element(by.partialButtonText('Login')); - - language = { - button: element(by.id('languageDropdownButton')), - links: element(by.id('languageDropdownMenu')).all(by.className('dropdown-item')), - findItem: (search: string) => this.language.links.filter(elem => elem.getText().then(text => text === search)) - }; -} diff --git a/test/app/bellows/shared/project-settings.page.ts b/test/app/bellows/shared/project-settings.page.ts deleted file mode 100644 index ad51f53f14..0000000000 --- a/test/app/bellows/shared/project-settings.page.ts +++ /dev/null @@ -1,60 +0,0 @@ -import {browser, by, element, ExpectedConditions} from 'protractor'; - -import { ProjectsPage } from './projects.page'; - -export class BellowsProjectSettingsPage { - private readonly projectsPage = new ProjectsPage(); - - conditionTimeout: number = 12000; - settingsMenuLink = element(by.id('settings-dropdown-button')); - projectSettingsLink = element(by.id('dropdown-project-settings')); - - // Get the projectSettings for project projectName - async get(projectName: string) { - await this.projectsPage.get(); - await this.projectsPage.clickOnProject(projectName); - await browser.wait(ExpectedConditions.visibilityOf(this.settingsMenuLink), this.conditionTimeout); - await this.settingsMenuLink.click(); - await browser.wait(ExpectedConditions.visibilityOf(this.projectSettingsLink), this.conditionTimeout); - return this.projectSettingsLink.click(); - } - - noticeList = element.all(by.repeater('notice in $ctrl.notices()')); - firstNoticeCloseButton = this.noticeList.first().element(by.buttonText('×')); - - tabDivs = element.all(by.className('tab-pane')); - activePane = element(by.css('div.tab-pane.active')); - - /* - Would like to use id locators, but the pui-tab directive that is used in the project settingsPage - in scripture forge is currently making it hard to assign an id to the tab element. This should be - updated, but due to time shortage, it will be left as is. - Mark W 2018-01-15 - */ - tabs = { - project: element(by.linkText('Project Properties')), - // reports: element(by.linkText('Reports')), // This feature is never tested - // archive: element(by.linkText('Archive')), // This is a disabled feature - remove: element(by.linkText('Delete')) - }; - - projectTab = { - name: element(by.model('project.projectName')), - code: element(by.model('project.projectCode')), - projectOwner: element(by.binding('project.ownerRef.username')), - saveButton: element(by.id('project-properties-save-button')) - }; - - // placeholder since we don't have Reports tests - reportsTab = { - }; - - // Archive tab currently disabled - // this.archiveTab = { - // archiveButton: this.activePane.element(by.buttonText('Archive this project')) - // }; - - deleteTab = { - deleteBoxText: this.activePane.element(by.id('deletebox')), - deleteButton: this.activePane.element(by.id('deleteProject')) - }; -} diff --git a/test/app/bellows/shared/projects.page.ts b/test/app/bellows/shared/projects.page.ts deleted file mode 100644 index 9383788953..0000000000 --- a/test/app/bellows/shared/projects.page.ts +++ /dev/null @@ -1,135 +0,0 @@ -import {browser, by, element, ExpectedConditions, protractor} from 'protractor'; -import {UserManagementPage} from './user-management.page'; -import {Utils} from './utils'; - -export class ProjectsPage { - private readonly utils = new Utils(); - private readonly userManagementPage = new UserManagementPage(); - - url = '/app/projects'; - get() { - return browser.get(browser.baseUrl + this.url); - } - - testProjectName = 'Test Project'; - createBtn = element(by.id('startJoinProjectButton')); - // Or just select "100" from the per-page dropdown, then you're pretty much guaranteed the Test - // Project will be on page 1, and you can find it. - itemsPerPageCtrl = element(by.model('itemsPerPage')); - projectsList = element.all(by.repeater('project in visibleProjects')); - projectNames = element.all(by.repeater('project in visibleProjects').column('project.projectName')); - - findProject(projectName: string) { - let foundRow: any; - const result = protractor.promise.defer(); - const searchName = new RegExp(projectName); - this.projectsList.map((row: any) => { - row.getText().then((text: string) => { - if (searchName.test(text)) { - foundRow = row; - } - }); - }).then(() => { - if (foundRow) { - result.fulfill(foundRow); - } else { - result.reject('Project ' + projectName + ' not found.'); - } - }); - - return result.promise; - } - - clickOnProject(projectName: string) { - return this.findProject(projectName).then((projectRow: any) => { - const projectLink = projectRow.element(by.css('a')); - projectLink.getAttribute('href').then((url: string) => { - browser.get(url); - }); - }); - } - - settingsBtn = element(by.id('settingsBtn')); - userManagementLink = (browser.baseUrl.includes('languageforge')) ? - element(by.id('userManagementLink')) : element(by.id('dropdown-project-settings')); - - addUserToProject(projectName: any, usersName: string, roleText: string) { - return this.findProject(projectName).then(async (projectRow: any) => { - const projectLink = projectRow.element(by.css('a')); - await projectLink.getAttribute('href').then((href: string) => { - const results = /app\/lexicon\/([0-9a-fA-F]+)\//.exec(href); - expect(results).not.toBeNull(); - expect(results.length).toBeGreaterThan(1); - const projectId = results[1]; - UserManagementPage.get(projectId); - }); - - await browser.wait(ExpectedConditions.visibilityOf(this.userManagementPage.addMembersBtn), Utils.conditionTimeout); - await this.userManagementPage.addMembersBtn.click(); - await browser.wait(ExpectedConditions.visibilityOf(this.userManagementPage.userNameInput), Utils.conditionTimeout); - await this.userManagementPage.userNameInput.sendKeys(usersName); - - await this.utils.findRowByText(this.userManagementPage.typeaheadItems, usersName).then((item: any) => { - item.click(); - }); - - // This should be unique no matter what - await this.userManagementPage.newMembersDiv.element(by.id('addUserButton')).click(); - - // Now set the user to member or manager, as needed - let foundUserRow: any; - await this.userManagementPage.projectMemberRows.map((row: any) => { - const nameColumn = row.element(by.binding('user.username')); - nameColumn.getText().then((text: string) => { - if (text === usersName) { - foundUserRow = row; - } - }); - }).then(() => { - if (foundUserRow) { - const select = foundUserRow.element(by.css('select')); - Utils.clickDropdownByValue(select, roleText); - } - }); - - return this.get(); // After all is finished, reload projects page - }); - } - - //noinspection JSUnusedGlobalSymbols - addManagerToProject(projectName: string, usersName: string) { - return this.addUserToProject(projectName, usersName, 'Manager'); - } - - addMemberToProject(projectName: string, usersName: string) { - return this.addUserToProject(projectName, usersName, 'Contributor'); - } - - removeUserFromProject(projectName: string, userName: string) { - return this.findProject(projectName).then(async (projectRow: any) => { - const projectLink = projectRow.element(by.css('a')); - await projectLink.getAttribute('href').then((href: string) => { - const results = /app\/lexicon\/([0-9a-fA-F]+)\//.exec(href); - expect(results).not.toBeNull(); - expect(results.length).toBeGreaterThan(1); - const projectId = results[1]; - return UserManagementPage.get(projectId); - }); - await browser.wait(ExpectedConditions.visibilityOf(this.userManagementPage.addMembersBtn), Utils.conditionTimeout); - - let userFilter: any; - let projectMemberRows: any; - userFilter = element(by.model('$ctrl.userFilter')); - await userFilter.sendKeys(userName); - projectMemberRows = element.all(by.repeater('user in $ctrl.list.visibleUsers')); - - const foundUserRow = await projectMemberRows.first(); - const rowCheckbox = foundUserRow.element(by.css('input[type="checkbox"]')); - await this.utils.setCheckbox(rowCheckbox, true); - const removeMembersBtn = element(by.id('remove-members-button')); - await removeMembersBtn.click(); - - return this.get(); // After all is finished, reload projects page - }); - } -} diff --git a/test/app/bellows/shared/reset-password.page.ts b/test/app/bellows/shared/reset-password.page.ts deleted file mode 100644 index ed34560451..0000000000 --- a/test/app/bellows/shared/reset-password.page.ts +++ /dev/null @@ -1,15 +0,0 @@ -import {browser, by, element} from 'protractor'; - -export class BellowsResetPasswordPage { - static get(resetPasswordKey: string) { - return browser.get(browser.baseUrl + '/auth/reset_password/' + resetPasswordKey); - } - - form = element(by.id('reset-password-form')); - // infoMessage and errors are dynamic elements, so class name locators seem to be the best option - infoMessages = element.all(by.className('alert-info')); - errors = element.all(by.className('alert-danger')); - passwordInput = element(by.model('$ctrl.record.password')); - confirmPasswordInput = element(by.model('$ctrl.confirmPassword')); - resetButton = element(by.id('reset-password-btn')); -} diff --git a/test/app/bellows/shared/signup.page.ts b/test/app/bellows/shared/signup.page.ts deleted file mode 100644 index 7d249c888d..0000000000 --- a/test/app/bellows/shared/signup.page.ts +++ /dev/null @@ -1,58 +0,0 @@ -import {browser, by, element} from 'protractor'; - -export class SignupPage { - static get() { - return browser.get(browser.baseUrl + '/public/signup'); - } - - static getPrefilledEmail(email: string) { - return browser.get(browser.baseUrl + '/public/signup#!/?e=' + encodeURIComponent(email)); - } - - signupForm = element(by.id('signupForm')); - emailInput = element(by.id('email')); - emailInvalid = element(by.id('emailInvalid')); - emailTaken = element(by.id('emailTaken')); - - nameInput = element(by.id('name')); - - visiblePasswordInvalid = element(by.id('visiblePasswordInvalid')); - passwordInput = element(by.id('password')); - passwordIsWeak = element(by.id('passwordIsWeak')); - showPassword = element(by.model('$ctrl.showPassword')); - - captchaDiv = element(by.id('pui-captcha')); - captcha = { - expectedItemName: this.captchaDiv.element(by.id('expectedItemName')), - blueSquareButton: this.captchaDiv.element(by.id('captcha0')), - yellowCircleButton: this.captchaDiv.element(by.id('captcha1')), - redTriangleButton: this.captchaDiv.element(by.id('captcha2')), - - setInvalidCaptcha: async () => { - await this.captcha.blueSquareButton.click(); - return this.captcha.expectedItemName.getText().then((result: string) => { - if (result === 'Blue Square') { - element(by.id('pui-captcha')).element(by.id('captcha1')).click(); - } - }); - }, - - setValidCaptcha: () => { - return this.captcha.expectedItemName.getText().then((result: string) => { - const captchaDiv = element(by.id('pui-captcha')); - switch (result) { - case 'Blue Square' : - return captchaDiv.element(by.id('captcha0')).click(); - case 'Yellow Circle' : - return captchaDiv.element(by.id('captcha1')).click(); - case 'Red Triangle' : - return captchaDiv.element(by.id('captcha2')).click(); - } - }); - } - }; - - captchaInvalid = element(by.id('captchaInvalid')); - signupButton = element(by.id('submit')); - noticeList = element.all(by.repeater('notice in $ctrl.notices()')); -} diff --git a/test/app/bellows/shared/site-admin.page.ts b/test/app/bellows/shared/site-admin.page.ts deleted file mode 100644 index 57cb9d6099..0000000000 --- a/test/app/bellows/shared/site-admin.page.ts +++ /dev/null @@ -1,56 +0,0 @@ -import {browser, by, element} from 'protractor'; -import { Utils } from './utils'; - -export class SiteAdminPage { - private readonly util = new Utils(); - - url = browser.baseUrl + '/app/siteadmin'; - get() { - // todo: refactor this to be a click recipe (as a user would click on the menu to navigate) - return browser.get(this.url); - } - - activePane = element(by.css('div.tab-pane.active')); - - tabs = { - reports: element(by.id('users')), - archivedProjects: element(by.id('archivedprojects')) - }; - - archivedProjectsTab = { - republishButton: element(by.id('site-admin-republish-btn')), - deleteButton: element(by.id('site-admin-delete-btn')), - projectsList: element.all(by.repeater('project in visibleProjects')), - setCheckbox: (row: number, value: boolean) => { - const projectRow = this.archivedProjectsTab.projectsList.get(row); - const rowCheckbox = projectRow.element(by.css('input[type="checkbox"]')); - return this.util.setCheckbox(rowCheckbox, value); - } - }; - - //noinspection JSUnusedGlobalSymbols - addBtn = element(by.id('site-admin-add-new-btn')); - - //noinspection JSUnusedGlobalSymbols - userFilterInput = element(by.model('filterUsers')); - usernameInput = element(by.model('record.username')); - nameInput = element(by.model('record.name')); - emailInput = element(by.model('record.email')); - - //noinspection JSUnusedGlobalSymbols - roleInput = element(by.model('record.role')); - - //noinspection JSUnusedGlobalSymbols - activeCheckbox = element(by.model('record.active')); - passwordInput = element(by.model('record.password')); - - //noinspection JSUnusedGlobalSymbols - async clearForm() { - await this.usernameInput.clear(); - await this.nameInput.clear(); - await this.emailInput.clear(); - return this.passwordInput.clear(); - - // this.activeCheckbox.clear(); - } -} diff --git a/test/app/bellows/shared/user-management.page.ts b/test/app/bellows/shared/user-management.page.ts deleted file mode 100644 index 7016f0b0f6..0000000000 --- a/test/app/bellows/shared/user-management.page.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { browser, by, element, ElementFinder, protractor } from 'protractor'; -import { ProjectsPage } from './projects.page'; -import { Utils } from './utils'; - -export class UserManagementPage { - static get(projectId: string) { - return browser.get(browser.baseUrl + '/app/usermanagement/' + projectId ); - } - - static async getByProjectName(projectName: string) { - const projectsPage = new ProjectsPage(); - await projectsPage.get(); - return projectsPage.findProject(projectName).then((projectRow: ElementFinder) => { - const projectLink = projectRow.element(by.css('a')); - return projectLink.getAttribute('href').then((href: string) => { - const results = /app\/lexicon\/([0-9a-fA-F]+)\//.exec(href); - expect(results).not.toBeNull(); - expect(results.length).toBeGreaterThan(1); - const projectId = results[1]; - return UserManagementPage.get(projectId); - }); - }); - } - - addMembersBtn = element(by.id('addMembersButton')); - newMembersDiv = element(by.id('newMembersDiv')); - projectMemberRows = element.all(by.repeater('user in $ctrl.list.visibleUsers')); - typeaheadDiv = element(by.id('typeaheadDiv')); - typeaheadItems = this.typeaheadDiv.all(by.css('ul li')); - userNameInput = this.newMembersDiv.element(by.id('typeaheadInput')); - - changeUserRole(userName: string, roleText: string) { - return this.getUserRow(userName).then( (row: ElementFinder) => { - if (row) { - const select = row.element(by.css('select')); - return Utils.clickDropdownByValue(select, roleText); - } - }); - } - - getUserRow(userName: string) { - const result = protractor.promise.defer(); - let foundUserRow: ElementFinder; - this.projectMemberRows.map((row: any) => { - const nameColumn = row.element(by.binding('user.username')); - nameColumn.getText().then( (text: string) => { - if (text === userName) { - foundUserRow = row; - } - }); - }).then(() => { - if (foundUserRow) { - result.fulfill(foundUserRow); - } else { - result.reject('User ' + userName + ' not found.'); - } - }); - - return result.promise; - } -} diff --git a/test/app/bellows/shared/user-profile.page.ts b/test/app/bellows/shared/user-profile.page.ts deleted file mode 100644 index 915365931e..0000000000 --- a/test/app/bellows/shared/user-profile.page.ts +++ /dev/null @@ -1,91 +0,0 @@ -import {browser, by, element, ExpectedConditions, protractor} from 'protractor'; - -import {Utils} from './utils'; - -export class SfUserProfilePage { - userProfileURL = '/app/userprofile'; - activitiesList = element.all(by.repeater('item in filteredActivities')); - - // Navigate to the MyProfile page (defaults to My Account tab) - get() { - return browser.get(browser.baseUrl + this.userProfileURL); - } - - // Navigate to the MyProfile -> My Account page - getMyAccount() { - return this.get(); - } - - // Navigate to the MyProfile -> About Me page - async getAboutMe() { - await this.get(); - await this.tabs.aboutMe.click(); - return browser.wait(ExpectedConditions.visibilityOf(this.aboutMeTab.fullName), Utils.conditionTimeout); - } - - tabs = { - myAccount: element(by.id('myAccountTab')), - aboutMe: element(by.id('AboutMeTab')) - }; - - blueElephantAvatarUri = '/Site/views/shared/image/avatar/DodgerBlue-elephant-128x128.png'; - goldPigAvatarUri = '/Site/views/shared/image/avatar/gold-pig-128x128.png'; - - myAccountTab = { - emailInput: element(by.id('email')), - username: element(by.id('username')), - - emailTaken: element(by.id('emailTaken')), - usernameTaken: element(by.id('usernameTaken')), - - avatarColor: element(by.id('user-profile-avatar-color')), - avatarShape: element(by.id('user-profile-avatar-shape')), - avatar: element(by.id('avatarRef')), - - saveBtn: element(by.id('saveBtn')), - - selectColor: (newColor: string|RegExp) => { - return Utils.clickDropdownByValue(this.myAccountTab.avatarColor, newColor); - }, - selectShape: (newShape: string|RegExp) => { - return Utils.clickDropdownByValue(this.myAccountTab.avatarShape, newShape); - }, - updateEmail: async (newEmail: string) => { - await browser.wait(ExpectedConditions.visibilityOf(this.myAccountTab.emailInput), Utils.conditionTimeout); - await this.myAccountTab.emailInput.sendKeys(protractor.Key.chord(protractor.Key.CONTROL, 'a')); - await this.myAccountTab.emailInput.sendKeys(newEmail); - - // click another field to force validation - return this.myAccountTab.username.click(); - }, - updateUsername: async (newUsername: string) => { - await browser.wait(ExpectedConditions.visibilityOf(this.myAccountTab.username), Utils.conditionTimeout); - await this.myAccountTab.username.sendKeys(protractor.Key.chord(protractor.Key.CONTROL, 'a')); - await this.myAccountTab.username.sendKeys(newUsername); - - // click another field to force validation - return this.myAccountTab.emailInput.click(); - }, - }; - - aboutMeTab = { - fullName: element(by.id('fullname')), - age: element(by.id('age')), - gender: element(by.id('gender')), - saveBtn: element(by.id('saveBtn')), - - updateFullName: async (newFullName: string) => { - await browser.wait(ExpectedConditions.visibilityOf(this.aboutMeTab.fullName), Utils.conditionTimeout); - await this.aboutMeTab.fullName.sendKeys(protractor.Key.chord(protractor.Key.CONTROL, 'a')); - return this.aboutMeTab.fullName.sendKeys(newFullName); - }, - updateAge: async (newAge: string) => { - await browser.wait(ExpectedConditions.visibilityOf(this.aboutMeTab.age), Utils.conditionTimeout); - await this.aboutMeTab.age.sendKeys(protractor.Key.chord(protractor.Key.CONTROL, 'a')); - return this.aboutMeTab.age.sendKeys(newAge); - }, - updateGender: (newGender: string) => { - return Utils.clickDropdownByValue(this.aboutMeTab.gender, newGender); - } - }; -} diff --git a/test/app/bellows/shared/utils.d.ts b/test/app/bellows/shared/utils.d.ts deleted file mode 100644 index c2d2fecf04..0000000000 --- a/test/app/bellows/shared/utils.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -import {Locator} from 'protractor'; -declare namespace protractor { - interface By { - elemMatches(selector: string, regexString: string): Locator; - } -} diff --git a/test/app/bellows/shared/utils.ts b/test/app/bellows/shared/utils.ts deleted file mode 100644 index cbad2f3881..0000000000 --- a/test/app/bellows/shared/utils.ts +++ /dev/null @@ -1,217 +0,0 @@ -// tslint:disable-next-line:no-reference -/// -import {browser, by, By, element, ExpectedConditions} from 'protractor'; -import {ElementArrayFinder, ElementFinder} from 'protractor/built/element'; -import {logging, WebElementPromise} from 'selenium-webdriver'; - -export class Utils { - static readonly conditionTimeout: number = 12000; - - setCheckbox(checkboxElement: ElementFinder, value: boolean) { - // Ensure a checkbox element will be either checked (true) or unchecked (false), regardless of - // what its current value is - return checkboxElement.isSelected().then(async (checked: boolean) => { - if (checked !== value) { - return checkboxElement.click(); - } - }); - } - - static findDropdownByValue(dropdownElement: ElementFinder, value: string|RegExp) { - // Simpler (MUCH simpler) approach based on our custom elemMatches locator (defined below) - return dropdownElement.element(By.cssContainingText('option', value)); - } - - static clickDropdownByValue(dropdownElement: ElementFinder, value: string|RegExp) { - // Select an element of the dropdown based on its value (its text) - return Utils.findDropdownByValue(dropdownElement, value).click(); - } - - findRowByFunc(elementArray: ElementArrayFinder, searchFunc: (rowText: string) => boolean): Promise { - // Repeater can be either a string or an already-created by.repeater() object - let foundRow: ElementFinder; - return new Promise((resolve, reject) => { - elementArray.map((row: ElementFinder) => { - row.getText().then((rowText: string) => { - if (searchFunc(rowText)) { - foundRow = row; - } - }); - }).then(() => { - if (foundRow) { - resolve(foundRow); - } else { - reject('Row not found'); - } - }); - }); - } - - findRowByText(elementArray: ElementArrayFinder, searchString: string): Promise { - return this.findRowByFunc(elementArray, (rowText: string) => { - return rowText.includes(searchString); - } - ); - } - - /* - * This method is an alternative to sendKeys(). It attempts to write the textString to the value - * of the element instead of sending one keystroke at a time - * - * @param elem - ElementFinder - * @param textString - string of text to set the value to - */ - static sendText(elem: ElementFinder, textString: string) { - return browser.executeScript('arguments[0].value = arguments[1];', elem.getWebElement(), textString); - } - - //noinspection JSUnusedGlobalSymbols - waitForAlert(timeout: number) { - if (!timeout) { timeout = 12000; } - - return browser.wait(() => { - let alertPresent = true; - try { - browser.switchTo().alert(); - } catch (NoSuchAlertError) { - alertPresent = false; - } - - return alertPresent; - }, timeout); - } - - private noticeList = element.all(by.repeater('notice in $ctrl.notices()')); - - notice: any = { - list: this.noticeList, - firstCloseButton: this.noticeList.first().element(by.partialButtonText('×')), - waitToInclude: async (includedText: any) => { - await browser.wait(() => - this.noticeList.count().then((count: any) => - count >= 1), - Utils.conditionTimeout); - return browser.wait(() => - this.noticeList.first().getText().then((text: any) => text.includes(includedText)), - Utils.conditionTimeout); - } - }; - - static async checkModalTextMatches(expectedText: string) { - const modalBody = element(by.css('.modal-body')); - - await browser.wait(ExpectedConditions.visibilityOf(modalBody), Utils.conditionTimeout); - return expect(modalBody.getText()).toMatch(expectedText); - } - - static async clickModalButton(buttonText: string) { - const button = element(by.css('.modal-footer')).element(by.partialButtonText(buttonText)); - - await browser.wait(ExpectedConditions.visibilityOf(button), Utils.conditionTimeout); - await browser.wait(ExpectedConditions.elementToBeClickable(button), Utils.conditionTimeout); - return button.click(); - } - - static clickBreadcrumb(breadcrumbTextOrRegex: string|RegExp) { - return element(by.cssContainingText('.breadcrumb > li', breadcrumbTextOrRegex)).click(); - } - - static parent(child: ElementFinder) { - return child.element(by.xpath('..')); - } - - // This handy function comes from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions - static escapeRegExp(stringToEscape: string) { - return stringToEscape.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string - } - - // Errors we choose to ignore because they are typically not encountered by users, but only - // in testing - static isMessageToIgnore(message: logging.Entry ) { - if (message.level.name === 'WARNING') return true; - - const text = message.message; - - return /angular.*\.js .* TypeError: undefined is not a function/.test(text) || - /\[\$compile:tpload] .* HTTP status: -1/.test(text) || - text.includes('password or credit card input in a non-secure context.') || - text.includes('ERR_INTERNET_DISCONNECTED'); - } - - static scrollTop() { - return browser.executeScript('window.scroll(0,0)'); - } - - static isAllCheckboxes(elementArray: ElementArrayFinder, state: boolean = true) { - const all: boolean[] = []; - return elementArray.each(async (checkboxElement: ElementFinder) => { - const isEnabled = await checkboxElement.isEnabled(); - if (isEnabled) { - all.push(await checkboxElement.isSelected()); - } - }).then(() => all.every((elem: boolean) => elem === state)); - } - - /** - * Usage: browser.executeScript(Utils.simulateDragDrop, source.getWebElement(), destination.getWebElement()); - * Adapted from https://gist.github.com/druska/624501b7209a74040175 - * @param {WebElementPromise} sourceNode - * @param {WebElementPromise} destinationNode - */ - static simulateDragDrop = (sourceNode: WebElementPromise, destinationNode: WebElementPromise) => { - const EVENT_TYPES = { - DRAG_END: 'dragend', - DRAG_START: 'dragstart', - DROP: 'drop' - }; - - function createCustomEvent(type: string): any { - const customEvent: any = new CustomEvent('CustomEvent'); - customEvent.initCustomEvent(type, true, true, null); - customEvent.dataTransfer = { - data: { - }, - setData(dataType: string, val: any) { - this.data[dataType] = val; - }, - getData(dataType: string) { - return this.data[dataType]; - } - }; - return customEvent; - } - - function dispatchEvent(node: any, type: string, customEvent: CustomEvent) { - if (node.dispatchEvent) { - return node.dispatchEvent(customEvent); - } - if (node.fireEvent) { - return node.fireEvent('on' + type, customEvent); - } - } - - const event = createCustomEvent(EVENT_TYPES.DRAG_START); - dispatchEvent(sourceNode, EVENT_TYPES.DRAG_START, event); - - const dropEvent = createCustomEvent(EVENT_TYPES.DROP); - dropEvent.dataTransfer = event.dataTransfer; - dispatchEvent(destinationNode, EVENT_TYPES.DROP, dropEvent); - - const dragEndEvent = createCustomEvent(EVENT_TYPES.DRAG_END); - dragEndEvent.dataTransfer = event.dataTransfer; - dispatchEvent(sourceNode, EVENT_TYPES.DRAG_END, dragEndEvent); - } - - static waitForNewAngularPage(pageToLoad: string): any { - // Switching between SPAs often creates a "document unloaded while waiting for result" error - // After extensive research, a forced sleep is the best solution availible as of June 2019 - return browser.driver.wait( () => { - return browser.driver.getCurrentUrl().then( url => { - return url.indexOf(pageToLoad) > -1; - }); - }, Utils.conditionTimeout).then( () => { - return browser.sleep(2000); - }); - } - -} diff --git a/test/app/bellows/signup.e2e-spec.ts b/test/app/bellows/signup.e2e-spec.ts deleted file mode 100644 index 4497fe6ef1..0000000000 --- a/test/app/bellows/signup.e2e-spec.ts +++ /dev/null @@ -1,128 +0,0 @@ -import {browser, ExpectedConditions} from 'protractor'; - -import {BellowsLoginPage} from './shared/login.page'; -import {SignupPage} from './shared/signup.page'; - -describe('Bellows E2E Signup app', () => { - const constants = require('./../testConstants.json'); - const page = new SignupPage(); - const loginPage = new BellowsLoginPage(); - - it('setup and contains a user form', async () => { - await BellowsLoginPage.logout(); - await SignupPage.get(); - expect(page.signupForm).toBeDefined(); - }); - - it('can verify required information filled', async () => { - await SignupPage.get(); - expect(await page.signupButton.isEnabled()).toBe(false); - await page.emailInput.sendKeys(constants.unusedEmail); - await page.nameInput.sendKeys(constants.unusedName); - await page.passwordInput.sendKeys(constants.passwordValid); - await page.captcha.setInvalidCaptcha(); - expect(await page.signupButton.isEnabled()).toBe(true); - }); - - it('cannot submit if email is invalid', async () => { - await page.emailInput.clear(); - await page.emailInput.sendKeys(constants.emailNoAt); - await page.captcha.setInvalidCaptcha(); - expect(await page.emailInvalid.isDisplayed()).toBe(true); - expect(await page.signupButton.isEnabled()).toBe(false); - }); - - it('cannot submit if password is weak', async () => { - await page.emailInput.clear(); - await page.emailInput.sendKeys(constants.unusedEmail); - expect(await page.signupButton.isEnabled()).toBe(true); - await page.passwordInput.clear(); - await page.passwordInput.sendKeys(constants.passwordTooShort); - await page.captcha.setInvalidCaptcha(); - expect(await page.passwordIsWeak.isDisplayed()).toBe(true); - expect(await page.signupButton.isEnabled()).toBe(false); - }); - - it('can submit if password not weak', async () => { - await page.passwordInput.clear(); - await page.passwordInput.sendKeys(constants.passwordValid); - await page.captcha.setInvalidCaptcha(); - expect(await page.passwordIsWeak.isDisplayed()).toBe(false); - expect(await page.signupButton.isEnabled()).toBe(true); - }); - - it('can submit if password is showing and not weak', async () => { - await page.showPassword.click(); - await page.captcha.setInvalidCaptcha(); - expect(await page.passwordIsWeak.isDisplayed()).toBe(false); - expect(await page.signupButton.isEnabled()).toBe(true); - }); - - it('cannot submit if captcha not selected', async () => { - await SignupPage.get(); - await page.emailInput.sendKeys(constants.unusedEmail); - await page.nameInput.sendKeys(constants.unusedName); - await page.passwordInput.sendKeys(constants.passwordValid); - expect(await page.signupButton.isEnabled()).toBe(false); - }); - - it('can submit a user registration request and captcha is invalid', async () => { - await SignupPage.get(); - await page.emailInput.sendKeys(constants.unusedEmail); - await page.nameInput.sendKeys(constants.unusedName); - await page.passwordInput.sendKeys(constants.passwordValid); - await page.captcha.setInvalidCaptcha(); - await page.signupButton.click(); - expect(await page.captchaInvalid.isDisplayed()).toBe(true); - }); - - it('finds the admin user (case insensitive) already exists', async () => { - await SignupPage.get(); - await page.emailInput.sendKeys(constants.adminEmail.toUpperCase()); - await page.nameInput.sendKeys(constants.unusedName); - await page.passwordInput.sendKeys(constants.passwordValid); - await page.captcha.setValidCaptcha(); - expect(await page.signupButton.isEnabled()).toBe(true); - await page.signupButton.click(); - expect(await page.emailTaken.isDisplayed()).toBe(true); - }); - - it('can prefill email address that can\'t be changed', async () => { - await SignupPage.getPrefilledEmail(constants.unusedEmail); - expect(await page.emailInput.isEnabled()).toBe(false); - }); - - it('can prefill email address that already exists', async () => { - await SignupPage.getPrefilledEmail(constants.adminEmail); - await page.nameInput.sendKeys(constants.unusedName); - await page.passwordInput.sendKeys(constants.passwordValid); - await page.captcha.setValidCaptcha(); - await page.signupButton.click(); - expect(await page.emailTaken.isDisplayed()).toBe(true); - }); - - it('can signup a new user', async () => { - await SignupPage.get(); - await page.emailInput.sendKeys(constants.unusedEmail); - await page.nameInput.sendKeys(constants.unusedName); - await page.passwordInput.sendKeys(constants.passwordValid); - await page.captcha.setValidCaptcha(); - expect(await page.signupButton.isEnabled()).toBe(true); - await page.signupButton.click(); - - // added to stop intermittent error - // "Failed: javascript error: document unloaded while waiting for result" - await browser.wait(ExpectedConditions.urlContains('projects'), constants.conditionTimeout); - - // Verify new user logged in and redirected to projects page - expect(await browser.getCurrentUrl()).toContain('/app/projects'); - }); - - it('redirects to projects page if already logged in', async () => { - await BellowsLoginPage.logout(); - await loginPage.loginAsUser(); - await SignupPage.get(); - await browser.wait(ExpectedConditions.urlContains('projects'), constants.conditionTimeout); - expect(await browser.getCurrentUrl()).toContain('/app/projects'); - }); -}); diff --git a/test/app/bellows/user-management.e2e-spec.ts b/test/app/bellows/user-management.e2e-spec.ts deleted file mode 100644 index e869e13b5d..0000000000 --- a/test/app/bellows/user-management.e2e-spec.ts +++ /dev/null @@ -1,86 +0,0 @@ -import {by, browser, ExpectedConditions} from 'protractor'; -import {ElementFinder} from 'protractor/built/element'; - -import {BellowsLoginPage} from './shared/login.page'; -import {ProjectsPage} from './shared/projects.page'; -import {UserManagementPage} from './shared/user-management.page'; -import { Utils } from './shared/utils'; - -describe('Bellows E2E User Management App', () => { - const constants = require('../testConstants.json'); - const loginPage = new BellowsLoginPage(); - const projectsPage = new ProjectsPage(); - const userManagementPage = new UserManagementPage(); - - it('Can add admin as Tech Support', async () => { - // Remove admin from Other Project for Testing - await loginPage.loginAsManager(); - await projectsPage.get(); - await projectsPage.removeUserFromProject(constants.otherProjectName, constants.adminUsername); - - await loginPage.loginAsAdmin(); - await projectsPage.get(); - - // Click "+ Tech Support" button - await projectsPage.findProject(constants.otherProjectName).then((projectRow: ElementFinder) => { - return projectRow.element(by.id('techSupportButton')).click(); - }); - - return UserManagementPage.getByProjectName(constants.otherProjectName).then(async () => { - await browser.wait(ExpectedConditions.visibilityOf(userManagementPage.projectMemberRows.first()), Utils.conditionTimeout); - return userManagementPage.getUserRow(constants.adminUsername).then(async (row: ElementFinder) => { - const selector = row.element(by.css('select')); - expect(await selector.isEnabled()).toBe(true); - return selector.element(by.css('option[selected=selected]')).getText().then( text => { - expect(text).toBe('Tech Support'); - }); - }); - }); - }); - - it('Other user cannot assign Tech Support user\'s role', async () => { - await loginPage.loginAsManager(); - return UserManagementPage.getByProjectName(constants.otherProjectName).then(async () => { - await browser.wait(ExpectedConditions.visibilityOf(userManagementPage.projectMemberRows.first()), Utils.conditionTimeout); - return userManagementPage.getUserRow(constants.adminUsername).then(async (row: ElementFinder) => { - const selector = row.element(by.css('select')); - expect(await selector.isEnabled()).toBe(false); - return selector.element(by.css('option[selected=selected]')).getText().then( text => { - expect(text).toBe('Tech Support'); - }); - }); - }); - - }); - - it('Tech Support user can assign their own role', async () => { - await loginPage.loginAsAdmin(); - await UserManagementPage.getByProjectName(constants.otherProjectName).then(async () => { - await browser.wait(ExpectedConditions.visibilityOf(userManagementPage.projectMemberRows.first()), Utils.conditionTimeout); - return userManagementPage.changeUserRole(constants.adminUsername, 'Manager'); - }); - // Now verify - return UserManagementPage.getByProjectName(constants.otherProjectName).then(async () => { - await browser.wait(ExpectedConditions.visibilityOf(userManagementPage.projectMemberRows.first()), Utils.conditionTimeout); - userManagementPage.getUserRow(constants.adminUsername).then(async (row: ElementFinder) => { - const selector = row.element(by.css('select')); - expect(await selector.isEnabled()).toBe(true); - return selector.element(by.css('option[selected=selected]')).getText().then( text => { - expect(text).toBe('Manager'); - }); - }); - }); - }); - - it('User cannot assign member to Tech Support role', async () => { - await loginPage.loginAsManager(); - await UserManagementPage.getByProjectName(constants.otherProjectName); - const row = await userManagementPage.getUserRow(constants.adminUsername) as ElementFinder; - const text = await row.element(by.css('select')).getText(); - expect(text).toContain('Manager'); - expect(text).toContain('Contributor'); - expect(text).toContain('Observer'); - expect(text).toContain('Observer with comment'); - expect(text).not.toContain('Tech Support'); - }); -}); diff --git a/test/app/bellows/user-profile.e2e-spec.ts b/test/app/bellows/user-profile.e2e-spec.ts deleted file mode 100644 index 87dfcf4bb0..0000000000 --- a/test/app/bellows/user-profile.e2e-spec.ts +++ /dev/null @@ -1,226 +0,0 @@ -import {browser, ExpectedConditions} from 'protractor'; - -import {BellowsLoginPage} from './shared/login.page'; -import {SfUserProfilePage} from './shared/user-profile.page'; -import {Utils} from './shared/utils'; - -describe('Bellows E2E User Profile app', () => { - const constants = require('../testConstants.json'); - const loginPage = new BellowsLoginPage(); - const userProfile = new SfUserProfilePage(); - - // Array of test usernames to test Activity page with different roles - const usernames = [constants.memberUsername, constants.managerUsername]; - const newUsername = 'newusername'; - // Run the Activity E2E as each test user - usernames.forEach(expectedUsername => { - - const newEmail = 'newemail@example.com'; - let newColor: string; - let newShape: string; - let newMobilePhone: string; - let expectedAvatar: string; - let originalEmail: string; - let password: string; - let expectedFullname: string; - - switch (expectedUsername) { - case constants.memberUsername: - newColor = 'Blue'; - newShape = 'Elephant'; - newMobilePhone = '+1876 5555555'; - expectedAvatar = userProfile.blueElephantAvatarUri; - originalEmail = constants.memberEmail; - password = constants.memberPassword; - expectedFullname = constants.memberName; - break; - case constants.managerUsername: - newColor = 'Gold'; - newShape = 'Pig'; - newMobilePhone = '+1876 911'; - expectedAvatar = userProfile.goldPigAvatarUri; - originalEmail = constants.managerEmail; - password = constants.managerPassword; - expectedFullname = constants.managerName; - break; - } - - function logInAsRole() { - if (expectedUsername === constants.memberUsername) return loginPage.loginAsMember(); - else if (expectedUsername === constants.managerUsername) return loginPage.loginAsManager(); - } - - // Perform activity E2E tests according to the different roles - describe('Running as: ' + expectedUsername, () => { - it('Logging in', async () => { - await logInAsRole(); - }); - - it('Verify initial "My Account" settings created from setupTestEnvironment.php', async () => { - await userProfile.getMyAccount(); - - expect(await userProfile.myAccountTab.username.getAttribute('value')).toEqual(expectedUsername); - expect(await userProfile.myAccountTab.avatar.getAttribute('src')).toContain(constants.avatar); - expect(await userProfile.myAccountTab.avatarColor.$('option:checked').getText()) - .toBe('Select a Color...'); - expect(await userProfile.myAccountTab.avatarShape.$('option:checked').getText()) - .toBe('Choose an animal...'); - expect(await userProfile.myAccountTab.mobilePhoneInput.getAttribute('value')).toEqual(''); - expect(await userProfile.myAccountTab.emailBtn.isSelected()); - }); - - it('Verify initial "About Me" settings created from setupTestEnvironment.php', async () => { - await userProfile.getAboutMe(); - - const expectedAge: string = ''; - const expectedGender: string = ''; - - expect(await userProfile.aboutMeTab.fullName.getAttribute('value')).toEqual(expectedFullname); - expect(await userProfile.aboutMeTab.age.getAttribute('value')).toEqual(expectedAge); - expect(await userProfile.aboutMeTab.gender.$('option:checked').getText()).toBe(expectedGender); - }); - - it('Update and store "My Account" settings', async () => { - await userProfile.getMyAccount(); - - // Change profile except username - - await userProfile.myAccountTab.updateEmail(newEmail); - - // Ensure "Blue" won't match "Steel Blue", etc. - await userProfile.myAccountTab.selectColor(new RegExp('^' + newColor + '$')); - await userProfile.myAccountTab.selectShape(newShape); - - await userProfile.myAccountTab.updateMobilePhone(newMobilePhone); - - // Modify contact preference - await userProfile.myAccountTab.bothBtn.click(); - - // Change Password tested in changepassword e2e - - // Submit updated profile - await userProfile.myAccountTab.saveBtn.click(); - }); - - it('Verify that new profile settings persisted', async () => { - await browser.refresh(); - await browser.wait(ExpectedConditions.visibilityOf(userProfile.myAccountTab.emailInput), constants.conditionTimeout); - - // Verify values. - expect(await userProfile.myAccountTab.emailInput.getAttribute('value')).toEqual(newEmail); - expect(await userProfile.myAccountTab.avatar.getAttribute('src')).toContain(expectedAvatar); - expect(await userProfile.myAccountTab.avatarColor.$('option:checked').getText()).toBe(newColor); - expect(await userProfile.myAccountTab.avatarShape.$('option:checked').getText()).toBe(newShape); - expect(await userProfile.myAccountTab.mobilePhoneInput.getAttribute('value')).toEqual(newMobilePhone); - expect(await userProfile.myAccountTab.bothBtn.isSelected()); - - // Restore email address - await userProfile.myAccountTab.updateEmail(originalEmail); - await userProfile.myAccountTab.saveBtn.click().then(async () => { - await browser.refresh(); - await browser.wait(ExpectedConditions.visibilityOf(userProfile.myAccountTab.emailInput), - constants.conditionTimeout); - }); - - expect(await userProfile.myAccountTab.emailInput.getAttribute('value')).toEqual(originalEmail); - }); - - it('Update and store different username. Login with new credentials', async () => { - // Change email - await userProfile.myAccountTab.updateEmail(newEmail); - - // Change to taken username - await userProfile.myAccountTab.updateUsername(constants.observerUsername); - await browser.wait(ExpectedConditions.visibilityOf(userProfile.myAccountTab.usernameTaken), - constants.conditionTimeout); - expect(await userProfile.myAccountTab.usernameTaken.isDisplayed()).toBe(true); - expect(await userProfile.myAccountTab.saveBtn.isEnabled()).toBe(false); - - // Change to new username - await userProfile.myAccountTab.updateUsername(newUsername); - expect(await userProfile.myAccountTab.usernameTaken.isDisplayed()).toBe(false); - - // Save, Cancel the confirmation modal - expect(await userProfile.myAccountTab.saveBtn.isEnabled()).toBe(true); - await userProfile.myAccountTab.saveBtn.click(); - await Utils.clickModalButton('Cancel'); - await browser.wait(ExpectedConditions.elementToBeClickable(userProfile.myAccountTab.saveBtn)); - - await browser.refresh(); - - // Confirm email not changed - await browser.wait(ExpectedConditions.visibilityOf(userProfile.myAccountTab.emailInput), constants.conditionTimeout); - await browser.wait(ExpectedConditions.elementToBeClickable(userProfile.myAccountTab.saveBtn)); - await Utils.scrollTop(); - expect(await userProfile.myAccountTab.emailInput.getAttribute('value')).toEqual(originalEmail); - - // Change to new username - await userProfile.myAccountTab.updateUsername(newUsername); - expect(await userProfile.myAccountTab.usernameTaken.isDisplayed()).toBe(false); - - // Save changes - expect(await userProfile.myAccountTab.saveBtn.isEnabled()).toBe(true); - await userProfile.myAccountTab.saveBtn.click(); - - await Utils.clickModalButton('Save changes'); - await Utils.waitForNewAngularPage('login'); - await browser.wait(ExpectedConditions.visibilityOf(loginPage.username), constants.conditionTimeout); - expect(await loginPage.infoMessages.count()).toBe(1); - expect(await loginPage.infoMessages.first().getText()).toContain('Username changed. Please login.'); - }); - - it('Login with new username and revert to original username', async () => { - // user is automatically logged out and taken to login page when username is changed - await loginPage.login(newUsername, password); - - await userProfile.getMyAccount(); - expect(await userProfile.myAccountTab.username.getAttribute('value')).toEqual(newUsername); - await userProfile.myAccountTab.updateUsername(expectedUsername); - await userProfile.myAccountTab.saveBtn.click(); - await Utils.clickModalButton('Save changes'); - await BellowsLoginPage.get(); - }); - - it('Update and store "About Me" settings', async () => { - await logInAsRole(); - - await userProfile.getAboutMe(); - - // New user profile to put in - let newFullName: string; - let newAge: string; - let newGender: string; - - switch (expectedUsername) { - case constants.memberUsername: - newFullName = 'abracadabra'; - newAge = '3.1415'; - newGender = 'Female'; - break; - case constants.managerUsername: - newFullName = 'MrAdmin'; - newAge = '33.33'; - newGender = 'Male'; - break; - } - - // Modify About me - await userProfile.aboutMeTab.updateFullName(newFullName); - - await userProfile.aboutMeTab.updateAge(newAge); - await userProfile.aboutMeTab.updateGender(newGender); - - // Submit updated profile - await userProfile.aboutMeTab.saveBtn.click(); - expect(await userProfile.aboutMeTab.fullName.getAttribute('value')).toEqual(newFullName); - - // Verify values. Browse to different URL first to force new page load - await userProfile.getAboutMe(); - - expect(await userProfile.aboutMeTab.fullName.getAttribute('value')).toEqual(newFullName); - expect(await userProfile.aboutMeTab.age.getAttribute('value')).toEqual(newAge); - expect(await userProfile.aboutMeTab.gender.$('option:checked').getText()).toBe(newGender); - }); - }); - }); -}); diff --git a/test/app/chris's protractor api cheatsheet.md b/test/app/chris's protractor api cheatsheet.md deleted file mode 100644 index f378f9161e..0000000000 --- a/test/app/chris's protractor api cheatsheet.md +++ /dev/null @@ -1,117 +0,0 @@ -# Chris's Protractor API cheatsheet - -## [WebElement](http://selenium.googlecode.com/git/docs/api/javascript/class_webdriver_WebElement.html) (from WebDriver) - - .clear() - - .click() - - .findElement(locator) - - .findElements(locator) - - .getAttribute(name) - - .getCssValue(stylePropertyName) - - .getDriver() - - .getInnerHtml() - - .getLocation() - - .getOuterHtml() - - .getSize() - - .getTagName() - - .getText() - - .isDisplayed() - - .isElementPresent() - - .isEnabled() - - .isSelected() - - .sendKeys(stringOfKeys - multiple args OK) - - .submit() - if element is in a form - - .toWireValue() - to JSON - -## [Locators](http://selenium.googlecode.com/git/docs/api/javascript/namespace_webdriver_By.html) (from WebDriver) - - by.hash({id:'1', className:'foo'}) - - by.className(className) - - by.css(selector) - - by.id(id) - - by.js(script, args) - - by.linkText(text) - - by.name(name) - - by.partialLinkText(text) - - by.tagName(text) - - by.xpath(xpath) - -## [Locators](https://github.com/angular/protractor/blob/master/docs/api.md#locators) (additions from Protractor) - - by.binding - - by.select - - by.selectedOption - DEPRECATED, use element(by.model('foo')).$('option:checked') - - by.input - - by.model - - by.buttonText - - by.partialButtonText - - by.repeater - can chain .row(index) or .column(partialBindingText); - -## Protractor [ElementFinders](https://github.com/angular/protractor/blob/master/docs/api.md#protractor) - - element - - elementFinder.find - - elementFinder.isPresent - - elementFinder.element - - elementFinder.$ - - element.all - - elementArrayFinder.count - - elementArrayFinder.get - - elementArrayFinder.first - - elementArrayFinder.last - - elementArrayFinder.each - - elementArrayFinder.map - -## Protractor Browser - - browser.get(url) - - browser.pause() - - browser.debugger() diff --git a/test/app/e2eTestConfig.php b/test/app/e2eTestConfig.php deleted file mode 100644 index c00ca4c38a..0000000000 --- a/test/app/e2eTestConfig.php +++ /dev/null @@ -1,9 +0,0 @@ - { - const constants = require('../testConstants.json'); - const loginPage = new BellowsLoginPage(); - const configurationPage = new ConfigurationPage(); - const projectsPage = new ProjectsPage(); - const projectSettingsPage = new ProjectSettingsPage(); - const newLexProjectPage = new NewLexProjectPage(); - const editorPage = new EditorPage(); - - describe('Explore configuration page', () => { - it('Unified tab', async () => { - await loginPage.loginAsAdmin(); - await configurationPage.get(); - await configurationPage.tabs.unified.click(); - await configurationPage.unifiedPane.inputSystem.addGroupButton.click(); - await browser.$('body').sendKeys(protractor.Key.ESCAPE); - await browser.$('body').sendKeys(protractor.Key.ESCAPE); - await browser.wait(ExpectedConditions.elementToBeClickable(configurationPage.tabs.unified), constants.conditionTimeout); - await configurationPage.tabs.unified.click(); - await configurationPage.unifiedPane.entry.addGroupButton.click(); - await browser.$('body').sendKeys(protractor.Key.ESCAPE); - await browser.$('body').sendKeys(protractor.Key.ESCAPE); - await browser.wait(ExpectedConditions.elementToBeClickable(configurationPage.unifiedPane.sense.addGroupButton), - constants.conditionTimeout); - await configurationPage.unifiedPane.hiddenIfEmptyCheckbox('Citation Form').click(); - await configurationPage.unifiedPane.fieldSpecificButton('Citation Form').click(); - await configurationPage.unifiedPane.entry.fieldSpecificInputSystemCheckbox('Citation Form', 1).click(); - await configurationPage.unifiedPane.fieldSpecificButton('Citation Form').click(); - await configurationPage.unifiedPane.sense.addGroupButton.click(); - await browser.$('body').sendKeys(protractor.Key.ESCAPE); - await browser.$('body').sendKeys(protractor.Key.ESCAPE); - await browser.wait(ExpectedConditions.elementToBeClickable(configurationPage.unifiedPane.example.addGroupButton), - constants.conditionTimeout); - await configurationPage.unifiedPane.hiddenIfEmptyCheckbox('Pictures').click(); - await configurationPage.unifiedPane.fieldSpecificButton('Pictures').click(); - await configurationPage.unifiedPane.sense.fieldSpecificInputSystemCheckbox('Pictures', 1).click(); - await configurationPage.unifiedPane.fieldSpecificCaptionHiddenIfEmptyCheckbox('Pictures').click(); - await configurationPage.unifiedPane.fieldSpecificButton('Pictures').click(); - await configurationPage.unifiedPane.example.addGroupButton.click(); - await browser.$('body').sendKeys(protractor.Key.ESCAPE); - await browser.$('body').sendKeys(protractor.Key.ESCAPE); - await browser.wait(ExpectedConditions.elementToBeClickable(configurationPage.tabs.unified), constants.conditionTimeout); - await configurationPage.unifiedPane.hiddenIfEmptyCheckbox('Translation').click(); - await configurationPage.unifiedPane.fieldSpecificButton('Translation').click(); - await configurationPage.unifiedPane.example.fieldSpecificInputSystemCheckbox('Translation', 0).click(); - await configurationPage.unifiedPane.fieldSpecificButton('Translation').click(); - }); - }); - - describe('Explore editor page', () => { - it('Edit view', async () => { - await projectsPage.get(); - await projectsPage.clickOnProject(constants.testProjectName); - await editorPage.edit.clickEntryByLexeme(constants.testEntry1.lexeme.th.value); - await editorPage.noticeList.count(); - await editorPage.edit.entriesList.count(); - await editorPage.edit.senses.count(); - }); - - it('Comments view', async () => { - await editorPage.edit.toCommentsLink.click(); - await editorPage.comment.commentsList.count(); - }); - }); - - it('Explore new lex project page', async () => { - await NewLexProjectPage.get(); - await newLexProjectPage.noticeList.count(); - }); - - it('Explore project settings page', async () => { - await projectSettingsPage.get(constants.testProjectName); - await projectSettingsPage.tabs.project.click(); - }); - - // it('Explore project settings page', async () => { - // await projectSettingsPage.get(constants.testProjectName); - // await projectSettingsPage.noticeList.count(); - // await projectSettingsPage.tabDivs.count(); - // await projectSettingsPage.tabs.project.click(); - // await projectSettingsPage.tabs.remove.click(); - // }); - - // TODO this is an lf-specific view - // it('Explore user management page', async function() { - // await userManagementPage.get(); - // // TODO click on things - // }); -}); diff --git a/test/app/languageforge/lexicon/editor/editor-comments.e2e-spec.ts b/test/app/languageforge/lexicon/editor/editor-comments.e2e-spec.ts deleted file mode 100644 index 1a9c0c7696..0000000000 --- a/test/app/languageforge/lexicon/editor/editor-comments.e2e-spec.ts +++ /dev/null @@ -1,148 +0,0 @@ -import {browser, ExpectedConditions} from 'protractor'; - -import { protractor } from 'protractor/built/ptor'; -import {BellowsLoginPage} from '../../../bellows/shared/login.page'; -import {ProjectsPage} from '../../../bellows/shared/projects.page'; -import {EditorPage} from '../shared/editor.page'; - -describe('Lexicon E2E Editor Comments', () => { - const constants = require('../../../testConstants.json'); - const loginPage = new BellowsLoginPage(); - const projectsPage = new ProjectsPage(); - const editorPage = new EditorPage(); - - it('setup: login, click on test project', async () => { - await loginPage.loginAsManager(); - await projectsPage.get(); - await projectsPage.clickOnProject(constants.testProjectName); - await editorPage.edit.toListLink.click(); - }); - - it('browse page has correct word count', async () => { - // flaky assertion, also test/app/languageforge/lexicon/editor/e2e/editor-entry.spec.js:20 - expect(await editorPage.browse.entriesList.count()).toEqual(await editorPage.browse.getEntryCount()); - expect(await editorPage.browse.getEntryCount()).toBe(3); - }); - - it('click on first word', async () => { - await editorPage.browse.clickEntryByLexeme(constants.testEntry1.lexeme.th.value); - }); - - it('click first comment bubble, type in a comment, add text to another part of the entry, ' + - 'submit comment to appear on original field', async () => { - await editorPage.comment.bubbles.first.click(); - await editorPage.comment.newComment.textarea.sendKeys('First comment on this word.'); - await editorPage.edit.getMultiTextInputs('Definition').first().sendKeys('change value - '); - await editorPage.comment.newComment.postBtn.click(); - }); - - it('comments panel: check that comment shows up', async () => { - const comment = editorPage.comment.getComment(0); - expect(await comment.contextGuid.getAttribute('textContent')).toEqual('lexeme.th'); // flaky - - // Earlier tests modify the avatar and name of the manager user; don't check those - expect(await comment.score.getText()).toEqual('0 Likes'); - expect(await comment.plusOne.isPresent()).toBe(true); - expect(await comment.content.getText()).toEqual('First comment on this word.'); - expect(await comment.date.getText()).toMatch(/ago|in a few seconds|in less than a minute/); - }); - - it('comments panel: add comment to another part of the entry', async () => { - const definitionField = editorPage.edit.getMultiTextInputs('Definition').first(); - await definitionField.clear(); - await definitionField.sendKeys( - constants.testEntry1.senses[0].definition.en.value - ); - await editorPage.comment.bubbles.second.click(); - await editorPage.comment.newComment.textarea.clear(); - await editorPage.comment.newComment.textarea.sendKeys('Second comment.'); - await editorPage.comment.newComment.postBtn.click(); - }); - - it('comments panel: check that second comment shows up', async () => { - const comment = editorPage.comment.getComment(-1); - expect(await comment.wholeComment.isPresent()).toBe(true); - - // Earlier tests modify the avatar and name of the manager user; don't check those - expect(await comment.score.getText()).toEqual('0 Likes'); - expect(await comment.plusOne.isPresent()).toBe(true); - expect(await comment.content.getText()).toEqual('Second comment.'); - expect(await comment.date.getText()).toMatch(/ago|in a few seconds|in less than a minute/); - }); - - it('comments panel: check regarding value is hidden when the field value matches', async () => { - const comment = editorPage.comment.getComment(-1); - const updateText = 'update -'; - - // Make sure it is hidden - expect(await comment.regarding.container.isDisplayed()).toBe(false); - - // Change the field value and then make sure it appears - await editorPage.edit.getMultiTextInputs('Definition').first().sendKeys(updateText); - expect(await comment.regarding.container.isDisplayed()).toBe(true); - - // Make sure the regarding value matches what was originally there - const word = constants.testEntry1.senses[0].definition.en.value; - expect(await comment.regarding.fieldValue.getText()).toEqual(word); - // Restore original value of Definition to not distort other tests - for (let i = 0; i < updateText.length; i++) { - await editorPage.edit.getMultiTextInputs('Definition').first().sendKeys(protractor.Key.BACK_SPACE); - } - - // old stuff - // This comment should have a "regarding" section - }); - - it('comments panel: click +1 button on first comment', async () => { - const comment = editorPage.comment.getComment(0); - await editorPage.comment.bubbles.first.click(); - - // Should be clickable - expect(await comment.plusOneActive.getAttribute('data-ng-click')).not.toBe(null); - await comment.plusOneActive.click(); - expect(await comment.score.getText()).toEqual('1 Like'); - }); - - it('comments panel: +1 button disabled after clicking', async () => { - const comment = editorPage.comment.getComment(0); - expect(await comment.plusOneInactive.isDisplayed()).toBe(true); - - // Should NOT be clickable - expect(await comment.plusOneInactive.getAttribute('data-ng-click')).toBe(null); - expect(await comment.score.getText()).toEqual('1 Like'); // Should not change from previous test - }); - - it('comments panel: refresh returns to comment', async () => { - const comment = editorPage.comment.getComment(0); - await browser.refresh(); - await browser.wait(ExpectedConditions.visibilityOf(editorPage.comment.bubbles.first), constants.conditionTimeout); - await editorPage.comment.bubbles.first.click(); - await browser.wait(ExpectedConditions.visibilityOf(editorPage.commentDiv), constants.conditionTimeout); - expect(await comment.content.getText()).toEqual('First comment on this word.'); - }); - - it('comments panel: close comments panel clicking on bubble', async () => { - await editorPage.comment.bubbles.first.click(); - await browser.wait(ExpectedConditions.invisibilityOf(editorPage.commentDiv), constants.conditionTimeout); - expect(await editorPage.commentDiv.getAttribute('class')).not.toContain('panel-visible'); - // Hiding the comments panel triggers an animation (in hideRightPanel() in editor.component.ts) that uses - // Angular's $interval() to animate hiding the panel, taking 1500 ms on large screens, or 500 ms on small ones. - // Since it uses $interval(), the animation isn't disabled during our test run. Also, only AFTER the animation - // completes will control.rightPanelVisible be set to false. But the 'panel-visible' attribute is removed - // BEFORE the animation begins, so our browser.wait() call returns 1500 ms too soon. Which means we have to - // wait for the animation to complete before subsequent tests will be ready to run. - 2019-08 RM - await browser.sleep(1500 + 250); // Extra 250 ms for paranoia - }); - - it('comments panel: show all comments', async () => { - await editorPage.edit.toCommentsLink.click(); - browser.wait(ExpectedConditions.visibilityOf(editorPage.commentDiv), constants.conditionTimeout); - expect(await editorPage.commentDiv.getAttribute('class')).toContain('panel-visible'); - }); - - it('comments panel: close all comments clicking on main comments button', async () => { - await editorPage.edit.toCommentsLink.click(); - await browser.wait(ExpectedConditions.invisibilityOf(editorPage.commentDiv), constants.conditionTimeout); - expect(await editorPage.commentDiv.getAttribute('class')).not.toContain('panel-visible'); - }); -}); diff --git a/test/app/languageforge/lexicon/editor/editor-entry.e2e-spec.ts b/test/app/languageforge/lexicon/editor/editor-entry.e2e-spec.ts deleted file mode 100644 index 190b63c917..0000000000 --- a/test/app/languageforge/lexicon/editor/editor-entry.e2e-spec.ts +++ /dev/null @@ -1,654 +0,0 @@ -import {browser, by, ExpectedConditions} from 'protractor'; - -import { protractor } from 'protractor/built/ptor'; -import {BellowsLoginPage} from '../../../bellows/shared/login.page'; -import {ProjectsPage} from '../../../bellows/shared/projects.page'; -import {Utils} from '../../../bellows/shared/utils'; -import {ConfigurationPage} from '../shared/configuration.page'; -import {EditorPage} from '../shared/editor.page'; -import {EditorUtil} from '../shared/editor.util'; - -describe('Lexicon E2E Editor List and Entry', () => { - const constants = require('../../../testConstants.json'); - const loginPage = new BellowsLoginPage(); - const projectsPage = new ProjectsPage(); - const util = new Utils(); - const editorPage = new EditorPage(); - const editorUtil = new EditorUtil(); - const configPage = new ConfigurationPage(); - - const lexemeLabel = 'Word'; - - it('setup: login, click on test project, go to browse/list view', async () => { - await loginPage.loginAsManager(); - await projectsPage.get(); - await projectsPage.clickOnProject(constants.testProjectName); - await editorPage.edit.toListLink.click(); - }); - - it('search function works correctly', async () => { - await editorPage.browse.search.input.sendKeys('asparagus'); - expect(await editorPage.browse.search.getMatchCount()).toBe(1); - await editorPage.browse.search.clearBtn.click(); - await editorPage.browse.search.input.sendKeys('Asparagus'); - expect(await editorPage.browse.search.getMatchCount()).toBe(1); - await editorPage.browse.search.clearBtn.click(); - }); - - it('refresh returns to list view', async () => { - await browser.refresh(); - expect(await editorPage.browse.getEntryCount()).toBe(3); - }); - - it('click on first word', async () => { - await editorPage.browse.clickEntryByLexeme(constants.testEntry1.lexeme.th.value); - }); - - it('refresh returns to entry view', async () => { - expect(await editorPage.edit.getFirstLexeme()).toEqual(constants.testEntry1.lexeme.th.value); - await browser.refresh(); - expect(await editorPage.edit.getFirstLexeme()).toEqual(constants.testEntry1.lexeme.th.value); - }); - - it('edit page has correct word count', async () => { - expect(await editorPage.edit.entriesList.count()).toEqual(await editorPage.edit.getEntryCount()); - expect(await editorPage.edit.getEntryCount()).toBe(3); - }); - - it('word 1: edit page has correct definition, part of speech', async () => { - expect(await editorUtil.getFieldValues('Definition')).toEqual([ - { en: constants.testEntry1.senses[0].definition.en.value } - ]); - expect(await editorUtil.getFieldValues('Part of Speech')).toEqual([ - editorUtil.expandPartOfSpeech(constants.testEntry1.senses[0].partOfSpeech.value) - ]); - }); - - it('dictionary citation reflects lexeme form', async () => { - expect(await editorPage.edit.renderedDiv.getText()).toContain(constants.testEntry1.lexeme.th.value); - expect(await editorPage.edit.renderedDiv.getText()).toContain(constants.testEntry1.lexeme['th-fonipa'].value); - expect(await editorPage.edit.renderedDiv.getText()).not.toContain('citation form'); - }); - - it('add citation form as visible field', async () => { - await configPage.get(); - await configPage.tabs.unified.click(); - await util.setCheckbox(configPage.unifiedPane.hiddenIfEmptyCheckbox('Citation Form'), false); - await configPage.applyButton.click(); - await Utils.clickBreadcrumb(constants.testProjectName); - await editorPage.edit.clickEntryByLexeme(constants.testEntry1.lexeme.th.value); - }); - - it('citation form field overrides lexeme form in dictionary citation view', async () => { - await editorPage.edit.showHiddenFields(); - const citationFormMultiTextInputs = editorPage.edit.getMultiTextInputs('Citation Form'); - await editorPage.edit.selectElement.sendKeys(citationFormMultiTextInputs.first(), 'citation form'); - expect(await editorPage.edit.renderedDiv.getText()).toContain('citation form'); - expect(await editorPage.edit.renderedDiv.getText()).not.toContain(constants.testEntry1.lexeme.th.value); - expect(await editorPage.edit.renderedDiv.getText()).toContain(constants.testEntry1.lexeme['th-fonipa'].value); - await editorPage.edit.selectElement.clear(citationFormMultiTextInputs.first()); - expect(await editorPage.edit.renderedDiv.getText()).not.toContain('citation form'); - expect(await editorPage.edit.renderedDiv.getText()).toContain(constants.testEntry1.lexeme.th.value); - expect(await editorPage.edit.renderedDiv.getText()).toContain(constants.testEntry1.lexeme['th-fonipa'].value); - await editorPage.edit.hideHiddenFields(); - }); - - it('one picture and caption is present', async () => { - expect(await editorPage.edit.pictures.getFileName(0)) - .toContain('_' + constants.testEntry1.senses[0].pictures[0].fileName); - expect(await editorPage.edit.pictures.getCaption(0)) - .toEqual({ en: constants.testEntry1.senses[0].pictures[0].caption.en.value }); - }); - - it('file upload drop box is displayed when Add Picture is clicked', async () => { - expect(await editorPage.edit.pictures.addPictureLink.isPresent()).toBe(true); - expect(await editorPage.edit.pictures.addDropBox.isDisplayed()).toBe(false); - expect(await editorPage.edit.pictures.addCancelButton.isDisplayed()).toBe(false); - - // fix problem with protractor not scrolling to element before click - await browser.driver.executeScript('arguments[0].scrollIntoView();', - editorPage.edit.pictures.addPictureLink.getWebElement()); - await editorPage.edit.pictures.addPictureLink.click(); - expect(await editorPage.edit.pictures.addPictureLink.isDisplayed()).toBe(false); - expect(await editorPage.edit.pictures.addDropBox.isDisplayed()).toBe(true); - }); - - it('file upload drop box is not displayed when Cancel Adding Picture is clicked', async () => { - expect(await editorPage.edit.pictures.addCancelButton.isDisplayed()).toBe(true); - await editorPage.edit.pictures.addCancelButton.click(); - expect(await editorPage.edit.pictures.addPictureLink.isPresent()).toBe(true); - expect(await editorPage.edit.pictures.addDropBox.isDisplayed()).toBe(false); - expect(await editorPage.edit.pictures.addCancelButton.isDisplayed()).toBe(false); - }); - - it('change config to show Pictures and hide captions', async () => { - await configPage.get(); - await configPage.tabs.unified.click(); - await util.setCheckbox(configPage.unifiedPane.hiddenIfEmptyCheckbox('Pictures'), false); - await configPage.unifiedPane.fieldSpecificButton('Pictures').click(); - await util.setCheckbox(configPage.unifiedPane.fieldSpecificCaptionHiddenIfEmptyCheckbox('Pictures'), true); - await configPage.applyButton.click(); - }); - - it('caption is hidden when empty if "Hidden if empty" is set in config', async () => { - await Utils.clickBreadcrumb(constants.testProjectName); - await editorPage.edit.clickEntryByLexeme(constants.testEntry1.lexeme.th.value); - await editorPage.edit.hideHiddenFields(); - expect(await editorPage.edit.pictures.captions.first().isDisplayed()).toBe(true); - await editorPage.edit.selectElement.clear(editorPage.edit.pictures.captions.first()); - expect(await editorPage.edit.pictures.captions.count()).toBe(0); - }); - - it('change config to show Pictures and show captions', async () => { - await configPage.get(); - await configPage.tabs.unified.click(); - await util.setCheckbox(configPage.unifiedPane.hiddenIfEmptyCheckbox('Pictures'), false); - await configPage.unifiedPane.fieldSpecificButton('Pictures').click(); - await util.setCheckbox(configPage.unifiedPane.fieldSpecificCaptionHiddenIfEmptyCheckbox('Pictures'), false); - await configPage.applyButton.click(); - }); - - it('when caption is empty, it is visible if "Hidden if empty" is cleared in config', async () => { - await Utils.clickBreadcrumb(constants.testProjectName); - await editorPage.edit.clickEntryByLexeme(constants.testEntry1.lexeme.th.value); - expect(await editorPage.edit.pictures.captions.first().isDisplayed()).toBe(true); - }); - - it('picture is removed when Delete is clicked', async () => { - expect(await editorPage.edit.pictures.images.first().isPresent()).toBe(true); - expect(await editorPage.edit.pictures.removeImages.first().isPresent()).toBe(true); - await editorPage.edit.pictures.removeImages.first().click(); - await Utils.clickModalButton('Delete Picture'); - expect(await editorPage.edit.pictures.images.count()).toBe(0); - }); - - it('change config to hide Pictures and hide captions', async () => { - await configPage.get(); - await configPage.tabs.unified.click(); - await util.setCheckbox(configPage.unifiedPane.hiddenIfEmptyCheckbox('Pictures'), true); - await configPage.unifiedPane.fieldSpecificButton('Pictures').click(); - await util.setCheckbox(configPage.unifiedPane.fieldSpecificCaptionHiddenIfEmptyCheckbox('Pictures'), true); - await configPage.applyButton.click(); - }); - - it('while Show Hidden Fields has not been clicked, Pictures field is hidden', async () => { - await Utils.clickBreadcrumb(constants.testProjectName); - await editorPage.edit.clickEntryByLexeme(constants.testEntry1.lexeme.th.value); - expect(await editorPage.edit.getFields('Pictures').count()).toBe(0); - await editorPage.edit.showHiddenFields(); - expect(await editorPage.edit.pictures.list.isPresent()).toBe(true); - await editorPage.edit.hideHiddenFields(); - expect(await editorPage.edit.getFields('Pictures').count()).toBe(0); - }); - - it('audio Input System is present, playable and has "more" control (manager)', async () => { - expect(await editorPage.edit.audio.playerIcons(lexemeLabel).count()).toEqual(1); - expect(await editorPage.edit.audio.playerIcons(lexemeLabel).first().isDisplayed()).toBe(true); - expect(await editorPage.edit.audio.playerIcons(lexemeLabel).first().getAttribute('class')).toContain('fa-play'); - expect(await editorPage.edit.audio.players(lexemeLabel).first().isDisplayed()).toBe(true); - expect(await editorPage.edit.audio.players(lexemeLabel).first().isEnabled()).toBe(true); - expect(await editorPage.edit.audio.moreControls(lexemeLabel).first().isDisplayed()).toBe(true); - expect(await editorPage.edit.audio.moreControls(lexemeLabel).first().isEnabled()).toBe(true); - expect(await editorPage.edit.audio.uploadButtons(lexemeLabel).first().isDisplayed()).toBe(false); - expect(await editorPage.edit.audio.downloadButtons(lexemeLabel).first().isDisplayed()).toBe(false); - }); - - it('file upload drop box is displayed when Upload is clicked', async () => { - expect(await editorPage.edit.audio.moreControls(lexemeLabel).first().isDisplayed()).toBe(true); - expect(await editorPage.edit.audio.uploadDropBoxes(lexemeLabel).first().isDisplayed()).toBe(false); - expect(await editorPage.edit.audio.uploadCancelButtons(lexemeLabel).first().isDisplayed()).toBe(false); - await editorPage.edit.audio.moreControls(lexemeLabel).first().click(); - await editorPage.edit.audio.moreUpload(lexemeLabel, 0).click(); - expect(await editorPage.edit.audio.moreControls(lexemeLabel).first().isDisplayed()).toBe(false); - expect(await editorPage.edit.audio.uploadDropBoxes(lexemeLabel).first().isDisplayed()).toBe(true); - }); - - it('file upload drop box is not displayed when Cancel Uploading Audio is clicked', async () => { - expect(await editorPage.edit.audio.uploadCancelButtons(lexemeLabel).first().isDisplayed()).toBe(true); - await editorPage.edit.audio.uploadCancelButtons(lexemeLabel).first().click(); - expect(await editorPage.edit.audio.moreControls(lexemeLabel).first().isDisplayed()).toBe(true); - expect(await editorPage.edit.audio.uploadDropBoxes(lexemeLabel).first().isDisplayed()).toBe(false); - expect(await editorPage.edit.audio.uploadCancelButtons(lexemeLabel).first().isDisplayed()).toBe(false); - }); - - it('click on second word (found by definition)', async () => { - await editorPage.edit.findEntryByDefinition(constants.testEntry2.senses[0].definition.en.value).click(); - }); - - it('word 2: audio Input System is not playable but has "upload" button (manager)', async () => { - expect(await editorPage.edit.audio.playerIcons(lexemeLabel).first().isPresent()).toBe(false); - expect(await editorPage.edit.audio.players(lexemeLabel).first().isPresent()).toBe(false); - expect(await editorPage.edit.audio.moreControls(lexemeLabel).first().isDisplayed()).toBe(false); - expect(await editorPage.edit.audio.uploadButtons(lexemeLabel).first().isDisplayed()).toBe(true); - expect(await editorPage.edit.audio.uploadButtons(lexemeLabel).first().isEnabled()).toBe(true); - expect(await editorPage.edit.audio.downloadButtons(lexemeLabel).first().isDisplayed()).toBe(false); - }); - - it('login as member, click on first word', async () => { - await loginPage.loginAsMember(); - await projectsPage.get(); - await projectsPage.clickOnProject(constants.testProjectName); - await editorPage.edit.toListLink.click(); - await editorPage.browse.clickEntryByLexeme(constants.testEntry1.lexeme.th.value); - }); - - it('audio Input System is present, playable and has "more" control (member)', async () => { - expect(await editorPage.edit.audio.playerIcons(lexemeLabel).count()).toEqual(1); - expect(await editorPage.edit.audio.playerIcons(lexemeLabel).first().isDisplayed()).toBe(true); - expect(await editorPage.edit.audio.playerIcons(lexemeLabel).first().getAttribute('class')).toContain('fa-play'); - expect(await editorPage.edit.audio.players(lexemeLabel).first().isDisplayed()).toBe(true); - expect(await editorPage.edit.audio.players(lexemeLabel).first().isEnabled()).toBe(true); - expect(await editorPage.edit.audio.moreControls(lexemeLabel).first().isDisplayed()).toBe(true); - expect(await editorPage.edit.audio.moreControls(lexemeLabel).first().isEnabled()).toBe(true); - expect(await editorPage.edit.audio.uploadButtons(lexemeLabel).first().isDisplayed()).toBe(false); - expect(await editorPage.edit.audio.downloadButtons(lexemeLabel).first().isDisplayed()).toBe(false); - }); - - it('click on second word (found by definition)', async () => { - await editorPage.edit.findEntryByDefinition(constants.testEntry2.senses[0].definition.en.value) - .click(); - }); - - it('word 2: audio Input System is not playable but has "upload" button (member)', async () => { - expect(await editorPage.edit.audio.playerIcons(lexemeLabel).first().isPresent()).toBe(false); - expect(await editorPage.edit.audio.players(lexemeLabel).first().isPresent()).toBe(false); - expect(await editorPage.edit.audio.moreControls(lexemeLabel).first().isDisplayed()).toBe(false); - expect(await editorPage.edit.audio.uploadButtons(lexemeLabel).first().isDisplayed()).toBe(true); - expect(await editorPage.edit.audio.uploadButtons(lexemeLabel).first().isEnabled()).toBe(true); - expect(await editorPage.edit.audio.downloadButtons(lexemeLabel).first().isDisplayed()).toBe(false); - }); - - it('login as observer, click on first word', async () => { - await loginPage.loginAsObserver(); - await projectsPage.get(); - await projectsPage.clickOnProject(constants.testProjectName); - await editorPage.edit.toListLink.click(); - await editorPage.browse.clickEntryByLexeme(constants.testEntry1.lexeme.th.value); - }); - - it('audio Input System is playable but does not have "more" control (observer)', async () => { - expect(await editorPage.edit.audio.playerIcons(lexemeLabel).count()).toEqual(1); - expect(await editorPage.edit.audio.playerIcons(lexemeLabel).first().isDisplayed()).toBe(true); - expect(await editorPage.edit.audio.playerIcons(lexemeLabel).first().getAttribute('class')).toContain('fa-play'); - expect(await editorPage.edit.audio.players(lexemeLabel).first().isDisplayed()).toBe(true); - expect(await editorPage.edit.audio.players(lexemeLabel).first().isEnabled()).toBe(true); - expect(await editorPage.edit.audio.moreControls(lexemeLabel).first().isDisplayed()).toBe(false); - expect(await editorPage.edit.audio.uploadButtons(lexemeLabel).first().isDisplayed()).toBe(false); - expect(await editorPage.edit.audio.downloadButtons(lexemeLabel).first().isDisplayed()).toBe(true); - }); - - it('click on second word (found by definition)', async () => { - await editorPage.edit.findEntryByDefinition(constants.testEntry2.senses[0].definition.en.value).click(); - }); - - it('word 2: audio Input System is not playable and does not have "upload" button (observer)', - async () => { - expect(await editorPage.edit.audio.playerIcons(lexemeLabel).first().isPresent()).toBe(false); - expect(await editorPage.edit.audio.players(lexemeLabel).first().isPresent()).toBe(false); - expect(await editorPage.edit.audio.moreControls(lexemeLabel).first().isDisplayed()).toBe(false); - expect(await editorPage.edit.audio.uploadButtons(lexemeLabel).first().isDisplayed()).toBe(false); - expect(await editorPage.edit.audio.downloadButtons(lexemeLabel).first().isDisplayed()).toBe(false); - }); - - it('login as manager, click on first word', async () => { - await loginPage.loginAsManager(); - await projectsPage.get(); - await projectsPage.clickOnProject(constants.testProjectName); - await editorPage.edit.toListLink.click(); - await editorPage.browse.clickEntryByLexeme(constants.testEntry1.lexeme.th.value); - }); - - it('can delete audio Input System', async () => { - expect(await editorPage.edit.audio.moreControls(lexemeLabel).first().isDisplayed()).toBe(true); - await editorPage.edit.audio.moreControls(lexemeLabel).first().click(); - await editorPage.edit.audio.moreDelete(lexemeLabel, 0).click(); - await Utils.clickModalButton('Delete Audio'); - expect(await editorPage.edit.audio.uploadButtons(lexemeLabel).first().isDisplayed()).toBe(true); - }); - - it('file upload drop box is displayed when Upload is clicked', async () => { - expect(await editorPage.edit.audio.uploadButtons(lexemeLabel).first().isDisplayed()).toBe(true); - expect(await editorPage.edit.audio.uploadDropBoxes(lexemeLabel).first().isDisplayed()).toBe(false); - expect(await editorPage.edit.audio.uploadCancelButtons(lexemeLabel).first().isDisplayed()).toBe(false); - await editorPage.edit.audio.uploadButtons(lexemeLabel).first().click(); - expect(await editorPage.edit.audio.uploadButtons(lexemeLabel).first().isDisplayed()).toBe(false); - expect(await editorPage.edit.audio.uploadDropBoxes(lexemeLabel).first().isDisplayed()).toBe(true); - }); - - it('file upload drop box is not displayed when Cancel Uploading Audio is clicked', async () => { - expect(await editorPage.edit.audio.uploadCancelButtons(lexemeLabel).first().isDisplayed()).toBe(true); - await editorPage.edit.audio.uploadCancelButtons(lexemeLabel).first().click(); - expect(await editorPage.edit.audio.uploadButtons(lexemeLabel).first().isDisplayed()).toBe(true); - expect(await editorPage.edit.audio.uploadDropBoxes(lexemeLabel).first().isDisplayed()).toBe(false); - expect(await editorPage.edit.audio.uploadCancelButtons(lexemeLabel).first().isDisplayed()).toBe(false); - }); - - describe('Mock file upload', async () => { - - it('can\'t upload a non-audio file', async () => { - expect(await editorPage.noticeList.count()).toBe(0); - await editorPage.edit.audio.uploadButtons(lexemeLabel).first().click(); - await editorPage.edit.audio.control(lexemeLabel, 0).mockUpload.enableButton.click(); - expect(await editorPage.edit.audio.control(lexemeLabel, 0).mockUpload.fileNameInput.isDisplayed()).toBe(true); - await editorPage.edit.audio.control(lexemeLabel, 0).mockUpload.fileNameInput - .sendKeys(constants.testMockPngUploadFile.name); - await editorPage.edit.audio.control(lexemeLabel, 0).mockUpload.fileSizeInput - .sendKeys(constants.testMockPngUploadFile.size); - await editorPage.edit.audio.control(lexemeLabel, 0).mockUpload.uploadButton.click(); - expect(await editorPage.noticeList.count()).toBe(1); - expect(await editorPage.noticeList.first().getText()).toContain(constants.testMockPngUploadFile.name + - ' is not an allowed audio file. Ensure the file is'); - expect(await editorPage.edit.audio.uploadDropBoxes(lexemeLabel).first().isDisplayed()).toBe(true); - await editorPage.edit.audio.control(lexemeLabel, 0).mockUpload.fileNameInput.clear(); - await editorPage.edit.audio.control(lexemeLabel, 0).mockUpload.fileSizeInput.clear(); - await editorPage.firstNoticeCloseButton.click(); - }); - - it('can upload an audio file', async () => { - expect(await editorPage.noticeList.count()).toBe(0); - await editorPage.edit.audio.control(lexemeLabel, 0).mockUpload.fileNameInput - .sendKeys(constants.testMockMp3UploadFile.name); - await editorPage.edit.audio.control(lexemeLabel, 0).mockUpload.fileSizeInput - .sendKeys(constants.testMockMp3UploadFile.size); - await editorPage.edit.audio.control(lexemeLabel, 0).mockUpload.uploadButton.click(); - await editorPage.edit.audio.control(lexemeLabel, 0).mockUpload.enableButton.click(); - expect(await editorPage.noticeList.count()).toBe(1); - expect(await editorPage.noticeList.first().getText()).toContain('File uploaded successfully'); - expect(await editorPage.edit.audio.playerIcons(lexemeLabel).first().isDisplayed()).toBe(true); - expect(await editorPage.edit.audio.playerIcons(lexemeLabel).first().getAttribute('class')).toContain('fa-play'); - expect(await editorPage.edit.audio.players(lexemeLabel).first().isDisplayed()).toBe(true); - expect(await editorPage.edit.audio.players(lexemeLabel).first().isEnabled()).toBe(true); - expect(await editorPage.edit.audio.moreControls(lexemeLabel).first().isDisplayed()).toBe(true); - }); - - }); - - it('click on second word (found by definition)', async () => { - await editorPage.edit.findEntryByDefinition(constants.testEntry2.senses[0].definition.en.value).click(); - }); - - it('word 2: edit page has correct definition, part of speech', async () => { - expect(await editorUtil.getFieldValues('Definition')).toEqual([ - { en: constants.testEntry2.senses[0].definition.en.value } - ]); - expect(await editorUtil.getFieldValues('Part of Speech')).toEqual([ - editorUtil.expandPartOfSpeech(constants.testEntry2.senses[0].partOfSpeech.value) - ]); - }); - - it('setup: click on word with multiple definitions (found by lexeme)', async () => { - await editorPage.edit.toListLink.click(); - await editorPage.browse.clickEntryByLexeme(constants.testMultipleMeaningEntry1.lexeme.th.value); - - // fix problem with protractor not scrolling to element before click - await browser.driver.executeScript('arguments[0].scrollIntoView();', - editorPage.edit.senses.first().getWebElement()); - await editorPage.edit.senses.first().click(); - }); - - it('dictionary citation reflects example sentences and translations', async () => { - expect(await editorPage.edit.renderedDiv.getText()).toContain( - constants.testMultipleMeaningEntry1.senses[0].examples[0].sentence.th.value); - expect(await editorPage.edit.renderedDiv.getText()).toContain( - constants.testMultipleMeaningEntry1.senses[0].examples[0].translation.en.value); - expect(await editorPage.edit.renderedDiv.getText()).toContain( - constants.testMultipleMeaningEntry1.senses[0].examples[1].sentence.th.value); - expect(await editorPage.edit.renderedDiv.getText()).toContain( - constants.testMultipleMeaningEntry1.senses[0].examples[1].translation.en.value); - expect(await editorPage.edit.renderedDiv.getText()).toContain( - constants.testMultipleMeaningEntry1.senses[1].examples[0].sentence.th.value); - expect(await editorPage.edit.renderedDiv.getText()).toContain( - constants.testMultipleMeaningEntry1.senses[1].examples[0].translation.en.value); - expect(await editorPage.edit.renderedDiv.getText()).toContain( - constants.testMultipleMeaningEntry1.senses[1].examples[1].sentence.th.value); - expect(await editorPage.edit.renderedDiv.getText()).toContain( - constants.testMultipleMeaningEntry1.senses[1].examples[1].translation.en.value); - }); - - it('word with multiple definitions: edit page has correct definitions, parts of speech', - async () => { - expect(await editorUtil.getFieldValues('Definition')).toEqual([ - { en: constants.testMultipleMeaningEntry1.senses[0].definition.en.value }, - { en: constants.testMultipleMeaningEntry1.senses[1].definition.en.value } - ]); - expect(await editorUtil.getFieldValues('Part of Speech')).toEqual([ - editorUtil.expandPartOfSpeech(constants.testMultipleMeaningEntry1.senses[0].partOfSpeech.value), - editorUtil.expandPartOfSpeech(constants.testMultipleMeaningEntry1.senses[1].partOfSpeech.value) - ]); - }); - - it('word with multiple meanings: edit page has correct example sentences, translations', async () => { - expect(await editorUtil.getFieldValues('Sentence')).toEqual([ - { th: constants.testMultipleMeaningEntry1.senses[0].examples[0].sentence.th.value }, - { th: constants.testMultipleMeaningEntry1.senses[0].examples[1].sentence.th.value }, - { th: constants.testMultipleMeaningEntry1.senses[1].examples[0].sentence.th.value }, - { th: constants.testMultipleMeaningEntry1.senses[1].examples[1].sentence.th.value } - ]); - expect(await editorUtil.getFieldValues('Translation')).toEqual([ - { en: constants.testMultipleMeaningEntry1.senses[0].examples[0].translation.en.value }, - { en: constants.testMultipleMeaningEntry1.senses[0].examples[1].translation.en.value }, - { en: constants.testMultipleMeaningEntry1.senses[1].examples[0].translation.en.value }, - { en: constants.testMultipleMeaningEntry1.senses[1].examples[1].translation.en.value } - ]); - }); - - it('while Show Hidden Fields has not been clicked, hidden fields are hidden if they are empty', async () => { - expect(await editorPage.edit.getFields('Semantics Note').count()).toBe(0); - expect(await editorPage.edit.getOneField('General Note').isPresent()).toBe(true); - await editorPage.edit.showHiddenFields(); - expect(await editorPage.edit.getOneField('Semantics Note').isPresent()).toBe(true); - expect(await editorPage.edit.getOneField('General Note').isPresent()).toBe(true); - }); - - it('word with multiple meanings: edit page has correct general notes, sources', async () => { - expect(await editorUtil.getFieldValues('General Note')).toEqual([ - { en: constants.testMultipleMeaningEntry1.senses[0].generalNote.en.value }, - { en: constants.testMultipleMeaningEntry1.senses[1].generalNote.en.value } - ]); - - // First item is empty Etymology Source, now that View Settings all default to visible. IJH - expect(await editorUtil.getFieldValues('Source')).toEqual([ - { en: constants.testMultipleMeaningEntry1.senses[0].source.en.value }, - { en: constants.testMultipleMeaningEntry1.senses[1].source.en.value } - ]); - }); - - it('senses can be reordered and deleted', async () => { - await editorPage.edit.sense.actionMenus.first().click(); - await editorPage.edit.sense.moveDown.first().click(); - expect(await editorUtil.getFieldValues('Definition')).toEqual([ - { en: constants.testMultipleMeaningEntry1.senses[1].definition.en.value }, - { en: constants.testMultipleMeaningEntry1.senses[0].definition.en.value } - ]); - }); - - it('back to browse page, create new word', async () => { - await editorPage.edit.toListLink.click(); - await editorPage.browse.newWordBtn.click(); - }); - - it('check that word count is still correct', async () => { - expect(await editorPage.edit.entriesList.count()).toEqual(await editorPage.edit.getEntryCount()); - expect(await editorPage.edit.getEntryCount()).toEqual(4); - }); - - it('modify new word', async () => { - const word = constants.testEntry3.lexeme.th.value; - const definition = constants.testEntry3.senses[0].definition.en.value; - await editorPage.edit.getMultiTextInputs(lexemeLabel).first().sendKeys(word); - await editorPage.edit.getMultiTextInputs('Definition').first().sendKeys(definition); - await Utils.clickDropdownByValue(await editorPage.edit.getOneField('Part of Speech').element(by.css('select')), - new RegExp('Noun \\(n\\)')); - await Utils.scrollTop(); - }); - - it('autosaves changes', async () => { - await browser.refresh(); - await browser.wait(ExpectedConditions.visibilityOf(await editorPage.edit.fields.last())); - await editorPage.edit.getMultiTextInputs(lexemeLabel).first().getAttribute('value').then(async text => { - await editorPage.edit.getMultiTextInputs(lexemeLabel).first().sendKeys('a'); - await browser.refresh(); - await browser.wait(ExpectedConditions.visibilityOf(await editorPage.edit.fields.last())); - expect(await editorPage.edit.getMultiTextInputs(lexemeLabel).first().getAttribute('value')).toEqual(text + 'a'); - await editorPage.edit.getMultiTextInputs(lexemeLabel).first().sendKeys(protractor.Key.BACK_SPACE); - }); - }); - - it('new word is visible in edit page', async () => { - await editorPage.edit.search.input.sendKeys(constants.testEntry3.senses[0].definition.en.value); - expect(await editorPage.edit.search.getMatchCount()).toBe(1); - await editorPage.edit.search.clearBtn.click(); - }); - - it('check that Semantic Domain field is visible (for view settings test later)', async () => { - await browser.wait(ExpectedConditions.visibilityOf(await editorPage.edit.fields.last())); - expect(await editorPage.edit.getOneField('Semantic Domain').isPresent()).toBeTruthy(); - }); - - describe('Configuration check', async () => { - const ipaRowLabel = /^Thai \(IPA\)$/; - const thaiAudioRowLabel = 'Thai Voice (Voice)'; - const englishRowLabel = 'English'; - - it('Word has only "th", "tipa" and "taud" visible', async () => { - expect(await editorPage.edit.getMultiTextInputSystems(lexemeLabel).count()).toBeGreaterThanOrEqual(3); - expect(await editorPage.edit.getMultiTextInputSystems(lexemeLabel).get(0).getText()).toEqual('th'); - expect(await editorPage.edit.getMultiTextInputSystems(lexemeLabel).get(1).getText()).toEqual('tipa'); - expect(await editorPage.edit.getMultiTextInputSystems(lexemeLabel).get(2).getText()).toEqual('taud'); - expect(await editorPage.edit.getMultiTextInputSystems(lexemeLabel).count()).toEqual(3); - }); - - it('make "en" input system visible for "Word" field', async () => { - await configPage.get(); - await configPage.tabs.unified.click(); - await configPage.unifiedPane.fieldSpecificButton(lexemeLabel).click(); - await util.setCheckbox( - configPage.unifiedPane.entry.fieldSpecificInputSystemCheckbox(lexemeLabel, englishRowLabel), true); - await configPage.applyButton.click(); - await Utils.clickBreadcrumb(constants.testProjectName); - await editorPage.edit.clickEntryByLexeme(constants.testEntry1.lexeme.th.value); - }); - - it('Word has "th", "tipa", "taud" and "en" visible', async () => { - expect(await editorPage.edit.getMultiTextInputSystems(lexemeLabel).count()).toBeGreaterThanOrEqual(4); - expect(await editorPage.edit.getMultiTextInputSystems(lexemeLabel).get(0).getText()).toEqual('th'); - expect(await editorPage.edit.getMultiTextInputSystems(lexemeLabel).get(1).getText()).toEqual('tipa'); - expect(await editorPage.edit.getMultiTextInputSystems(lexemeLabel).get(2).getText()).toEqual('taud'); - expect(await editorPage.edit.getMultiTextInputSystems(lexemeLabel).get(3).getText()).toEqual('en'); - expect(await editorPage.edit.getMultiTextInputSystems(lexemeLabel).count()).toEqual(4); - }); - - it('make "en" input system invisible for "Word" field', async () => { - await configPage.get(); - await configPage.tabs.unified.click(); - await configPage.unifiedPane.fieldSpecificButton(lexemeLabel).click(); - await util.setCheckbox( - configPage.unifiedPane.entry.fieldSpecificInputSystemCheckbox(lexemeLabel, englishRowLabel), false); - await configPage.applyButton.click(); - await Utils.clickBreadcrumb(constants.testProjectName); - await editorPage.edit.clickEntryByLexeme(constants.testEntry1.lexeme.th.value); - }); - - it('Word has only "th", "tipa" and "taud" visible again', async () => { - expect(await editorPage.edit.getMultiTextInputSystems(lexemeLabel).count()).toBeGreaterThanOrEqual(3); - expect(await editorPage.edit.getMultiTextInputSystems(lexemeLabel).get(0).getText()).toEqual('th'); - expect(await editorPage.edit.getMultiTextInputSystems(lexemeLabel).get(1).getText()).toEqual('tipa'); - expect(await editorPage.edit.getMultiTextInputSystems(lexemeLabel).get(2).getText()).toEqual('taud'); - expect(await editorPage.edit.getMultiTextInputSystems(lexemeLabel).count()).toEqual(3); - }); - - it('make "taud" input system invisible for "Word" field and "tipa" invisible for manager role', async () => { - await configPage.get(); - await configPage.tabs.unified.click(); - - // Ensure field-specific input systems for Word are visible - const wordChevron = await configPage.unifiedPane.fieldSpecificIcon(lexemeLabel).getAttribute('class'); - if (wordChevron.includes('fa-chevron-down')) { - await configPage.unifiedPane.fieldSpecificButton(lexemeLabel).click(); - } - - // Alternately, just do it regardless - // await configPage.unifiedPane.fieldSpecificButton(lexemeLabel).click(); - await util.setCheckbox( - configPage.unifiedPane.entry.fieldSpecificInputSystemCheckbox(lexemeLabel, thaiAudioRowLabel), false); - await util.setCheckbox(configPage.unifiedPane.managerCheckbox(ipaRowLabel), false); - - await configPage.applyButton.click(); - await Utils.clickBreadcrumb(constants.testProjectName); - }); - - it('Word has only "th" visible', async () => { - expect(await editorPage.edit.getMultiTextInputSystems(lexemeLabel).count()).toBeGreaterThanOrEqual(1); - expect(await editorPage.edit.getMultiTextInputSystems(lexemeLabel).get(0).getText()).toEqual('th'); - expect(await editorPage.edit.getMultiTextInputSystems(lexemeLabel).count()).toEqual(1); - }); - - it('restore visibility of "taud" for "Word" field', async () => { - await configPage.get(); - await configPage.tabs.unified.click(); - // Ensure field-specific input systems for Word are visible - const wordChevron = await configPage.unifiedPane.fieldSpecificIcon(lexemeLabel).getAttribute('class'); - if (wordChevron.includes('fa-chevron-down')) { - await configPage.unifiedPane.fieldSpecificButton(lexemeLabel).click(); - } - // await configPage.unifiedPane.fieldSpecificButton(lexemeLabel).click(); - await util.setCheckbox( - configPage.unifiedPane.entry.fieldSpecificInputSystemCheckbox(lexemeLabel, thaiAudioRowLabel), true); - await configPage.applyButton.click(); - await Utils.clickBreadcrumb(constants.testProjectName); - await editorPage.edit.clickEntryByLexeme(constants.testEntry1.lexeme.th.value); - }); - - it('Word has only "th" and "taud" visible for manager role', async () => { - expect(await editorPage.edit.getMultiTextInputSystems(lexemeLabel).count()).toBeGreaterThanOrEqual(2); - expect(await editorPage.edit.getMultiTextInputSystems(lexemeLabel).get(0).getText()).toEqual('th'); - expect(await editorPage.edit.getMultiTextInputSystems(lexemeLabel).get(1).getText()).toEqual('taud'); - expect(await editorPage.edit.getMultiTextInputSystems(lexemeLabel).count()).toEqual(2); - }); - - it('restore visibility of "tipa" input system for manager role', async () => { - await configPage.get(); - await configPage.tabs.unified.click(); - await util.setCheckbox(configPage.unifiedPane.managerCheckbox(ipaRowLabel), true); - await configPage.applyButton.click(); - await Utils.clickBreadcrumb(constants.testProjectName); - await editorPage.edit.clickEntryByLexeme(constants.testEntry1.lexeme.th.value); - }); - - it('Word has "th", "tipa" and "taud" visible again for manager role', async () => { - expect(await editorPage.edit.getMultiTextInputSystems(lexemeLabel).count()).toBeGreaterThanOrEqual(3); - expect(await editorPage.edit.getMultiTextInputSystems(lexemeLabel).get(0).getText()).toEqual('th'); - expect(await editorPage.edit.getMultiTextInputSystems(lexemeLabel).get(1).getText()).toEqual('tipa'); - expect(await editorPage.edit.getMultiTextInputSystems(lexemeLabel).get(2).getText()).toEqual('taud'); - expect(await editorPage.edit.getMultiTextInputSystems(lexemeLabel).count()).toEqual(3); - }); - - }); - - it('new word is visible in browse page', async () => { - await editorPage.edit.toListLink.click(); - await editorPage.browse.search.input.sendKeys(constants.testEntry3.senses[0].definition.en.value); - expect(await editorPage.browse.search.getMatchCount()).toBe(1); - await editorPage.browse.search.clearBtn.click(); - }); - - it('check that word count is still correct in browse page', async () => { - expect(await editorPage.browse.entriesList.count()).toEqual(await editorPage.browse.getEntryCount()); - expect(await editorPage.browse.getEntryCount()).toBe(4); - }); - - it('remove new word to restore original word count', async () => { - await editorPage.browse.clickEntryByLexeme(constants.testEntry3.lexeme.th.value); - await editorPage.edit.actionMenu.click(); - await editorPage.edit.deleteMenuItem.click(); - await browser.waitForAngular(); - await browser.wait(ExpectedConditions.visibilityOf(await editorPage.modal.modalBodyText)); - await Utils.clickModalButton('Delete Entry'); - await browser.waitForAngular(); - expect(await editorPage.edit.getEntryCount()).toBe(3); - }); - - it('previous entry is selected after delete', async () => { - expect(await editorPage.edit.getFirstLexeme()).toEqual(constants.testEntry1.lexeme.th.value); - }); -}); diff --git a/test/app/languageforge/lexicon/lexicon-new-project.e2e-spec.ts b/test/app/languageforge/lexicon/lexicon-new-project.e2e-spec.ts deleted file mode 100644 index a55a6782e5..0000000000 --- a/test/app/languageforge/lexicon/lexicon-new-project.e2e-spec.ts +++ /dev/null @@ -1,520 +0,0 @@ -import {browser, ExpectedConditions, Key} from 'protractor'; - -import {BellowsLoginPage} from '../../bellows/shared/login.page'; -import {Utils} from '../../bellows/shared/utils'; -import {EditorPage} from './shared/editor.page'; -import {NewLexProjectPage} from './shared/new-lex-project.page'; - -describe('Lexicon E2E New Project wizard app', () => { - const constants = require('../../testConstants.json'); - const loginPage = new BellowsLoginPage(); - const util = new Utils(); - const editorPage = new EditorPage(); - const page = new NewLexProjectPage(); - const CHECK_PAUSE = 1000; - - it('admin can get to wizard', async () => { - await loginPage.loginAsAdmin(); - await NewLexProjectPage.get(); - expect(await page.newLexProjectForm).toBeDefined(); - expect(await page.chooserPage.createButton.isDisplayed()).toBe(true); - }); - - it('manager can get to wizard', async () => { - await loginPage.loginAsManager(); - await NewLexProjectPage.get(); - expect(await page.newLexProjectForm).toBeDefined(); - expect(await page.chooserPage.createButton.isDisplayed()).toBe(true); - }); - - it('setup: user login and page contains a form', async () => { - await loginPage.loginAsUser(); - await NewLexProjectPage.get(); - expect(await page.newLexProjectForm).toBeDefined(); - expect(await page.chooserPage.createButton.isDisplayed()).toBe(true); - }); - - describe('Chooser page', () => { - - it('cannot see Back or Next buttons', async () => { - expect(await page.backButton.isDisplayed()).toBe(false); - expect(await page.nextButton.isDisplayed()).toBe(false); - await page.formStatus.expectHasNoError(); - }); - - it('can create a new project', async () => { - expect(await page.chooserPage.createButton.isEnabled()).toBe(true); - await page.chooserPage.createButton.click(); - expect(await page.namePage.projectNameInput.isDisplayed()).toBe(true); - }); - - it('can go back to Chooser page', async () => { - expect(await page.backButton.isDisplayed()).toBe(true); - await page.backButton.click(); - expect(await page.chooserPage.sendReceiveButton.isDisplayed()).toBe(true); - }); - - it('can select Send and Receive', async () => { - expect(await page.chooserPage.sendReceiveButton.isEnabled()).toBe(true); - await page.chooserPage.sendReceiveButton.click(); - expect(await page.srCredentialsPage.loginInput.isDisplayed()).toBe(true); - }); - - it('can go back to Chooser page', async () => { - expect(await page.backButton.isDisplayed()).toBe(true); - await page.backButton.click(); - expect(await page.chooserPage.sendReceiveButton.isDisplayed()).toBe(true); - }); - - }); - - describe('Send Receive Credentials page', () => { - - it('can get back to Send and Receive Credentials page', async () => { - await page.chooserPage.sendReceiveButton.click(); - expect(await page.srCredentialsPage.loginInput.isDisplayed()).toBe(true); - expect(await page.srCredentialsPage.loginInput.getAttribute('value')).toEqual(constants.memberUsername); - expect(await page.srCredentialsPage.passwordInput.isDisplayed()).toBe(true); - expect(await page.srCredentialsPage.projectSelect().isPresent()).toBe(false); - }); - - it('cannot move on if Password is empty', async () => { - await page.formStatus.expectHasNoError(); - expect(await page.nextButton.isEnabled()).toBe(true); - await page.nextButton.click(); - expect(await page.srCredentialsPage.loginInput.isDisplayed()).toBe(true); - expect(await page.srCredentialsPage.projectSelect().isPresent()).toBe(false); - await page.formStatus.expectContainsError('Password cannot be empty.'); - }); - - it('cannot move on if username is incorrect', async () => { - // passwordInvalid is, incredibly, an invalid password. - // It's valid only in the sense that it follows the password rules - await page.srCredentialsPage.passwordInput.sendKeys(constants.passwordValid); - await browser.wait(ExpectedConditions.visibilityOf(page.srCredentialsPage.credentialsInvalid), - constants.conditionTimeout); - expect(await page.srCredentialsPage.credentialsInvalid.isDisplayed()).toBe(true); - await page.formStatus.expectHasNoError(); - await page.nextButton.click(); - expect(await page.srCredentialsPage.loginInput.isDisplayed()).toBe(true); - expect(await page.srCredentialsPage.projectSelect().isPresent()).toBe(false); - await page.formStatus.expectContainsError('The username or password isn\'t valid on LanguageDepot.org.'); - }); - - it('can go back to Chooser page, user and pass preserved', async () => { - expect(await page.backButton.isDisplayed()).toBe(true); - await page.backButton.click(); - expect(await page.chooserPage.sendReceiveButton.isDisplayed()).toBe(true); - await page.chooserPage.sendReceiveButton.click(); - expect(await page.srCredentialsPage.loginInput.isDisplayed()).toBe(true); - expect(await page.srCredentialsPage.loginInput.getAttribute('value')).toEqual(constants.memberUsername); - expect(await page.srCredentialsPage.passwordInput.getAttribute('value')).toEqual(constants.passwordValid); - await page.srCredentialsPage.passwordInput.clear(); - }); - - it('cannot move on if Login is empty', async () => { - await page.srCredentialsPage.loginInput.clear(); - expect(await page.nextButton.isEnabled()).toBe(true); - await page.nextButton.click(); - expect(await page.srCredentialsPage.loginInput.isDisplayed()).toBe(true); - expect(await page.srCredentialsPage.projectSelect().isPresent()).toBe(false); - await page.formStatus.expectContainsError('Login cannot be empty.'); - }); - - it('cannot move on if credentials are invalid', async () => { - await page.srCredentialsPage.loginInput.sendKeys(constants.srUsername); - await page.srCredentialsPage.passwordInput.sendKeys(constants.passwordValid); - await browser.wait(ExpectedConditions.visibilityOf(page.srCredentialsPage.credentialsInvalid), - constants.conditionTimeout); - expect(await page.srCredentialsPage.loginOk.isPresent()).toBe(false); - await browser.wait(ExpectedConditions.visibilityOf(page.srCredentialsPage.credentialsInvalid), - constants.conditionTimeout); - expect(await page.srCredentialsPage.credentialsInvalid.isDisplayed()).toBe(true); - await page.formStatus.expectHasNoError(); - await page.nextButton.click(); - expect(await page.srCredentialsPage.loginInput.isDisplayed()).toBe(true); - expect(await page.srCredentialsPage.projectSelect().isPresent()).toBe(false); - }); - - it('can move on when the credentials are valid', async () => { - await page.srCredentialsPage.passwordInput.clear(); - await page.srCredentialsPage.passwordInput.sendKeys(constants.srPassword); - await browser.wait(ExpectedConditions.visibilityOf(page.srCredentialsPage.passwordOk), constants.conditionTimeout); - expect(await page.srCredentialsPage.loginOk.isDisplayed()).toBe(true); - expect(await page.srCredentialsPage.passwordOk.isDisplayed()).toBe(true); - expect(await page.srCredentialsPage.loginInput.isDisplayed()).toBe(true); - expect(await page.srCredentialsPage.projectSelect().isDisplayed()).toBe(true); - await page.formStatus.expectHasNoError(); - }); - - it('cannot move on if no project is selected', async () => { - await page.nextButton.click(); - expect(await page.srCredentialsPage.loginInput.isDisplayed()).toBe(true); - expect(await page.srCredentialsPage.projectSelect().isDisplayed()).toBe(true); - await page.formStatus.expectContainsError('Please select a Project.'); - }); - - it('cannot move on if not a manager of the project', async () => { - await Utils.clickDropdownByValue(page.srCredentialsPage.projectSelect(), 'mock-name2'); - expect(await page.srCredentialsPage.projectNoAccess.isDisplayed()).toBe(true); - await page.formStatus.expectContainsError('select a Project that you are the Manager of'); - }); - - it('can move on when a managed project is selected', async () => { - await Utils.clickDropdownByValue(page.srCredentialsPage.projectSelect(), 'mock-name4'); - expect(await page.srCredentialsPage.projectOk.isDisplayed()).toBe(true); - await page.formStatus.expectHasNoError(); - await page.expectFormIsValid(); - }); - - }); - - describe('Send Receive Verify page', () => { - - it('can clone project', async () => { - await page.nextButton.click(); - await browser.wait(ExpectedConditions.visibilityOf(page.srClonePage.cloning), constants.conditionTimeout); - expect(await page.srClonePage.cloning.isDisplayed()).toBe(true); - }); - - it('cannot move on while cloning', async () => { - expect(await page.nextButton.isDisplayed()).toBe(false); - expect(await page.nextButton.isEnabled()).toBe(false); - await page.expectFormIsNotValid(); - }); - - }); - - describe('New Project Name page', () => { - - it('can create a new project', async () => { - await NewLexProjectPage.get(); - await page.chooserPage.createButton.click(); - expect(await page.namePage.projectNameInput.isPresent()).toBe(true); - }); - - it('cannot move on if name is invalid', async () => { - expect(await page.nextButton.isEnabled()).toBe(true); - await page.nextButton.click(); - expect(await page.namePage.projectNameInput.isPresent()).toBe(true); - await page.formStatus.expectContainsError('Project Name cannot be empty.'); - }); - - it('finds the test project already exists', async () => { - await page.namePage.projectNameInput.sendKeys(constants.testProjectName + Key.TAB); - await browser.wait(ExpectedConditions.visibilityOf(page.namePage.projectCodeExists), constants.conditionTimeout); - expect(await page.namePage.projectCodeExists.isDisplayed()).toBe(true); - expect(await page.namePage.projectCodeAlphanumeric.isPresent()).toBe(false); - expect(await page.namePage.projectCodeOk.isPresent()).toBe(false); - expect(await page.namePage.projectCodeInput.getAttribute('value')).toEqual(constants.testProjectCode); - await page.formStatus.expectContainsError('Another project with code \'' + constants.testProjectCode + - '\' already exists.'); - }); - - it('with a cleared name does not show an error but is still invalid', async () => { - // Can't use projectNameInput.clear() as Selenium's clear() does *not* trigger Angular updates! - // See https://stackoverflow.com/questions/50677760/selenium-clear-command-doesnt-clear-the-element - await page.namePage.projectNameInput.sendKeys(Key.chord(Key.CONTROL, 'a'), Key.BACK_SPACE); - await browser.wait(ExpectedConditions.stalenessOf(page.namePage.projectCodeExists), constants.conditionTimeout); - expect(await page.namePage.projectCodeExists.isPresent()).toBe(false); - expect(await page.namePage.projectCodeAlphanumeric.isPresent()).toBe(false); - expect(await page.namePage.projectCodeOk.isPresent()).toBe(false); - await page.formStatus.expectHasNoError(); - expect(await page.nextButton.isEnabled()).toBe(true); - await page.nextButton.click(); - expect(await page.namePage.projectNameInput.isPresent()).toBe(true); - await page.formStatus.expectContainsError('Project Name cannot be empty.'); - }); - - it('can verify that an unused project name is available', async () => { - await page.namePage.projectNameInput.sendKeys(Key.chord(Key.CONTROL, 'a'), Key.BACK_SPACE); - await page.namePage.projectNameInput.sendKeys(constants.newProjectName + Key.TAB); - await browser.wait(ExpectedConditions.visibilityOf(page.namePage.projectCodeOk), - constants.conditionTimeout); - expect(await page.namePage.projectCodeOk.isDisplayed()).toBe(true); - expect(await page.namePage.projectCodeExists.isPresent()).toBe(false); - expect(await page.namePage.projectCodeAlphanumeric.isPresent()).toBe(false); - expect(await page.namePage.projectCodeInput.getAttribute('value')).toEqual(constants.newProjectCode); - await page.formStatus.expectHasNoError(); - }); - - it('can not edit project code by default', async () => { - expect(await page.namePage.projectCodeInput.isDisplayed()).toBe(false); - }); - - it('can edit project code when enabled', async () => { - expect(await page.namePage.editProjectCodeCheckbox.isDisplayed()).toBe(true); - await util.setCheckbox(page.namePage.editProjectCodeCheckbox, true); - expect(await page.namePage.projectCodeInput.isDisplayed()).toBe(true); - await page.namePage.projectCodeInput.sendKeys(Key.chord(Key.CONTROL, 'a'), Key.BACK_SPACE); - await page.namePage.projectCodeInput.sendKeys('changed_new_project'); - await page.namePage.projectNameInput.sendKeys(Key.TAB); // trigger project code check - expect(await page.namePage.projectCodeInput.getAttribute('value')).toEqual('changed_new_project'); - await page.formStatus.expectHasNoError(); - }); - - it('project code cannot be empty; does not show an error but is still invalid', async () => { - await page.namePage.projectCodeInput.sendKeys(Key.chord(Key.CONTROL, 'a'), Key.BACK_SPACE); - await page.namePage.projectCodeInput.sendKeys(Key.TAB); // trigger project code check - await browser.wait(ExpectedConditions.stalenessOf(page.namePage.projectCodeExists), constants.conditionTimeout); - expect(await page.namePage.projectCodeExists.isPresent()).toBe(false); - expect(await page.namePage.projectCodeAlphanumeric.isPresent()).toBe(false); - expect(await page.namePage.projectCodeOk.isPresent()).toBe(false); - await page.formStatus.expectHasNoError(); - expect(await page.nextButton.isEnabled()).toBe(true); - await page.nextButton.click(); - expect(await page.namePage.projectNameInput.isPresent()).toBe(true); - await page.formStatus.expectContainsError('Project Code cannot be empty.'); - }); - - it('project code can be one character', async () => { - await page.namePage.projectCodeInput.clear(); - await page.namePage.projectCodeInput.sendKeys('a'); - await page.namePage.projectNameInput.sendKeys(Key.TAB); // trigger project code check - await browser.wait(ExpectedConditions.visibilityOf(page.namePage.projectCodeOk), constants.conditionTimeout); - expect(await page.namePage.projectCodeExists.isPresent()).toBe(false); - expect(await page.namePage.projectCodeAlphanumeric.isPresent()).toBe(false); - expect(await page.namePage.projectCodeOk.isDisplayed()).toBe(true); - await page.formStatus.expectHasNoError(); - }); - - it('project code cannot be uppercase', async () => { - await page.namePage.projectCodeInput.clear(); - await page.namePage.projectCodeInput.sendKeys('A' + Key.TAB); - await browser.wait(ExpectedConditions.visibilityOf(page.namePage.projectCodeAlphanumeric), constants.conditionTimeout); - expect(await page.namePage.projectCodeExists.isPresent()).toBe(false); - expect(await page.namePage.projectCodeAlphanumeric.isDisplayed()).toBe(true); - expect(await page.namePage.projectCodeOk.isPresent()).toBe(false); - await page.formStatus.expectHasNoError(); - await page.nextButton.click(); - await page.formStatus.expectContainsError('Project Code must begin with a letter'); - await page.namePage.projectCodeInput.clear(); - await page.namePage.projectCodeInput.sendKeys('aB' + Key.TAB); - await browser.wait(ExpectedConditions.visibilityOf(page.namePage.projectCodeAlphanumeric), constants.conditionTimeout); - expect(await page.namePage.projectCodeExists.isPresent()).toBe(false); - expect(await page.namePage.projectCodeAlphanumeric.isDisplayed()).toBe(true); - expect(await page.namePage.projectCodeOk.isPresent()).toBe(false); - await page.formStatus.expectHasNoError(); - await page.nextButton.click(); - await page.formStatus.expectContainsError('Project Code must begin with a letter'); - }); - - it('project code cannot start with a number', async () => { - await page.namePage.projectCodeInput.clear(); - await page.namePage.projectCodeInput.sendKeys('1' + Key.TAB); - await browser.wait(ExpectedConditions.visibilityOf(page.namePage.projectCodeAlphanumeric), constants.conditionTimeout); - expect(await page.namePage.projectCodeExists.isPresent()).toBe(false); - expect(await page.namePage.projectCodeAlphanumeric.isDisplayed()).toBe(true); - expect(await page.namePage.projectCodeOk.isPresent()).toBe(false); - await page.formStatus.expectHasNoError(); - await page.nextButton.click(); - await page.formStatus.expectContainsError('Project Code must begin with a letter'); - }); - - it('project code cannot use non-alphanumeric', async () => { - await page.namePage.projectCodeInput.clear(); - await page.namePage.projectCodeInput.sendKeys('a?' + Key.TAB); - await browser.wait(ExpectedConditions.visibilityOf(page.namePage.projectCodeAlphanumeric), constants.conditionTimeout); - expect(await page.namePage.projectCodeExists.isPresent()).toBe(false); - expect(await page.namePage.projectCodeAlphanumeric.isDisplayed()).toBe(true); - expect(await page.namePage.projectCodeOk.isPresent()).toBe(false); - await page.formStatus.expectHasNoError(); - await page.nextButton.click(); - await page.formStatus.expectContainsError('Project Code must begin with a letter'); - }); - - it('project code reverts to default when Edit-project-code is disabled', async () => { - expect(await page.namePage.editProjectCodeCheckbox.isDisplayed()).toBe(true); - await util.setCheckbox(page.namePage.editProjectCodeCheckbox, false); - expect(await page.namePage.projectCodeInput.isDisplayed()).toBe(false); - expect(await page.namePage.projectCodeInput.getAttribute('value')).toEqual(constants.newProjectCode); - await page.formStatus.expectHasNoError(); - }); - - it('can create project', async () => { - expect(await page.nextButton.isEnabled()).toBe(true); - await page.expectFormIsValid(); - await page.nextButton.click(); - expect(await page.namePage.projectNameInput.isPresent()).toBe(false); - expect(await page.initialDataPage.browseButton.isPresent()).toBe(true); - await page.formStatus.expectHasNoError(); - }); - - }); - - describe('Initial Data page with upload', () => { - - it('cannot see back button and defaults to uploading data', async () => { - expect(await page.backButton.isDisplayed()).toBe(false); - expect(await page.initialDataPage.browseButton.isDisplayed()).toBe(true); - expect(await page.progressIndicatorStep3Label.getText()).toEqual('Verify'); - await page.expectFormIsNotValid(); - await page.formStatus.expectHasNoError(); - }); - - describe('Mock file upload', () => { - - it('cannot upload large file', async () => { - await page.initialDataPage.mockUpload.enableButton.click(); - expect(await page.initialDataPage.mockUpload.fileNameInput.isPresent()).toBe(true); - expect(await page.initialDataPage.mockUpload.fileNameInput.isDisplayed()).toBe(true); - await page.initialDataPage.mockUpload.fileNameInput.sendKeys(constants.testMockZipImportFile.name); - await page.initialDataPage.mockUpload.fileSizeInput.sendKeys(134217728); - expect(await page.noticeList.count()).toBe(0); - await page.initialDataPage.mockUpload.uploadButton.click(); - expect(await page.initialDataPage.browseButton.isDisplayed()).toBe(true); - expect(await page.verifyDataPage.entriesImported.isPresent()).toBe(false); - expect(await page.noticeList.count()).toBe(1); - expect(await page.noticeList.get(0).getText()).toContain('is too large. It must be smaller than'); - await page.formStatus.expectHasNoError(); - await page.initialDataPage.mockUpload.fileNameInput.clear(); - await page.initialDataPage.mockUpload.fileSizeInput.clear(); - await page.firstNoticeCloseButton.click(); - }); - - it('cannot upload jpg', async () => { - await page.initialDataPage.mockUpload.fileNameInput.sendKeys(constants.testMockJpgImportFile.name); - await page.initialDataPage.mockUpload.fileSizeInput.sendKeys(constants.testMockJpgImportFile.size); - expect(await page.noticeList.count()).toBe(0); - await page.initialDataPage.mockUpload.uploadButton.click(); - expect(await page.initialDataPage.browseButton.isDisplayed()).toBe(true); - expect(await page.verifyDataPage.entriesImported.isPresent()).toBe(false); - expect(await page.noticeList.count()).toBe(1); - expect(await page.noticeList.get(0).getText()).toContain(constants.testMockJpgImportFile.name + - ' is not an allowed compressed file. Ensure the file is'); - await page.formStatus.expectHasNoError(); - await page.initialDataPage.mockUpload.fileNameInput.clear(); - await page.initialDataPage.mockUpload.fileSizeInput.clear(); - await page.firstNoticeCloseButton.click(); - }); - - it('can upload zip file', async () => { - await page.initialDataPage.mockUpload.fileNameInput.sendKeys(constants.testMockZipImportFile.name); - await page.initialDataPage.mockUpload.fileSizeInput.sendKeys(constants.testMockZipImportFile.size); - expect(await page.noticeList.count()).toBe(0); - await page.initialDataPage.mockUpload.uploadButton.click(); - expect(await page.verifyDataPage.entriesImported.isDisplayed()).toBe(true); - expect(await page.noticeList.count()).toBe(1); - expect(await page.noticeList.get(0).getText()).toContain('Successfully imported ' + - constants.testMockZipImportFile.name); - await page.formStatus.expectHasNoError(); - }); - - }); - - }); - - describe('Verify Data await page', () => { - - it('displays stats', async () => { - expect(await page.verifyDataPage.title.getText()).toEqual('Verify Data'); - expect(await page.verifyDataPage.entriesImported.getText()).toEqual('2'); - await page.formStatus.expectHasNoError(); - }); - - // regression avoidance test - should not redirect when button is clicked - it('displays non-critical errors', async () => { - expect(await page.verifyDataPage.importErrors.isPresent()).toBe(true); - expect(await page.verifyDataPage.importErrors.isDisplayed()).toBe(false); - await page.verifyDataPage.nonCriticalErrorsButton.click(); - expect(await page.verifyDataPage.title.getText()).toEqual('Verify Data'); - await page.formStatus.expectHasNoError(); - expect(await page.verifyDataPage.importErrors.isDisplayed()).toBe(true); - expect(await page.verifyDataPage.importErrors.getText()) - .toContain('range file \'TestProj.lift-ranges\' was not found'); - await page.verifyDataPage.nonCriticalErrorsButton.click(); - await browser.wait(ExpectedConditions.invisibilityOf(page.verifyDataPage.importErrors), constants.conditionTimeout); - expect(await page.verifyDataPage.importErrors.isDisplayed()).toBe(false); - }); - - it('can go to lexicon', async () => { - expect(await page.nextButton.isDisplayed()).toBe(true); - expect(await page.nextButton.isEnabled()).toBe(true); - await page.expectFormIsValid(); - await page.nextButton.click(); - expect(editorPage.browse.getEntryCount()).toBe(2); - }); - - }); - - describe('New Empty Project Name page', () => { - - it('create: new empty project', async () => { - await NewLexProjectPage.get(); - await page.chooserPage.createButton.click(); - await page.namePage.projectNameInput.sendKeys(constants.emptyProjectName + Key.TAB); - await browser.wait(ExpectedConditions.visibilityOf(page.namePage.projectCodeOk), constants.conditionTimeout); - expect(await page.namePage.projectCodeExists.isPresent()).toBe(false); - expect(await page.namePage.projectCodeAlphanumeric.isPresent()).toBe(false); - expect(await page.namePage.projectCodeOk.isDisplayed()).toBe(true); - expect(await page.nextButton.isEnabled()).toBe(true); - - // added sleep to ensure state is stable so the next test passes (expectFormIsNotValid) - await browser.sleep(500); - await page.nextButton.click(); - expect(await page.namePage.projectNameInput.isPresent()).toBe(false); - expect(await page.initialDataPage.browseButton.isPresent()).toBe(true); - }); - - }); - - describe('Initial Data page skipping upload', () => { - - it('can skip uploading data', async () => { - expect(await page.nextButton.isEnabled()).toBe(true); - await page.expectFormIsNotValid(); - await page.nextButton.click(); - expect(await page.primaryLanguagePage.selectButton.isPresent()).toBe(true); - }); - - }); - - describe('Primary Language page', () => { - - it('can go back to initial data page (then forward again)', async () => { - expect(await page.backButton.isDisplayed()).toBe(true); - expect(await page.backButton.isEnabled()).toBe(true); - await page.backButton.click(); - expect(await page.initialDataPage.browseButton.isDisplayed()).toBe(true); - expect(await page.nextButton.isEnabled()).toBe(true); - await page.expectFormIsNotValid(); - await page.nextButton.click(); - expect(await page.primaryLanguagePage.selectButton.isPresent()).toBe(true); - expect(await page.backButton.isDisplayed()).toBe(true); - }); - - it('cannot move on if language is not selected', async () => { - expect(await page.nextButton.isEnabled()).toBe(true); - await page.expectFormIsNotValid(); - await page.nextButton.click(); - expect(await page.primaryLanguagePage.selectButton.isPresent()).toBe(true); - await page.formStatus.expectContainsError('Please select a primary language for the project.'); - }); - - it('can select language', async () => { - expect(await page.primaryLanguagePage.selectButton.isEnabled()).toBe(true); - await page.primaryLanguagePage.selectButtonClick(); - expect(await page.modal.selectLanguage.searchLanguageInput.isPresent()).toBe(true); - }); - - describe('Select Language modal', () => { - - it('can search, select and add language', async () => { - await page.modal.selectLanguage.searchLanguageInput.sendKeys(constants.searchLanguage + Key.ENTER); - expect(await page.modal.selectLanguage.languageRows.first().isPresent()).toBe(true); - - expect(await page.modal.selectLanguage.addButton.isPresent()).toBe(true); - expect(await page.modal.selectLanguage.addButton.isEnabled()).toBe(false); - await page.modal.selectLanguage.languageRows.first().click(); - expect(await page.modal.selectLanguage.addButton.isEnabled()).toBe(true); - expect(await page.modal.selectLanguage.addButton.getText()).toEqual('Add ' + constants.foundLanguage); - - await page.modal.selectLanguage.addButton.click(); - await browser.wait(ExpectedConditions.stalenessOf(page.modal.selectLanguage.searchLanguageInput), - constants.conditionTimeout); - expect(await page.modal.selectLanguage.searchLanguageInput.isPresent()).toBe(false); - }); - }); - }); - -}); diff --git a/test/app/languageforge/lexicon/settings/config-fields.e2e-spec.ts b/test/app/languageforge/lexicon/settings/config-fields.e2e-spec.ts deleted file mode 100644 index 5504bece99..0000000000 --- a/test/app/languageforge/lexicon/settings/config-fields.e2e-spec.ts +++ /dev/null @@ -1,717 +0,0 @@ -import {browser, protractor} from 'protractor'; - -import {BellowsLoginPage} from '../../../bellows/shared/login.page'; -import {ProjectsPage} from '../../../bellows/shared/projects.page'; -import {Utils} from '../../../bellows/shared/utils'; -import {ConfigurationPage} from '../shared/configuration.page'; -import {EditorPage} from '../shared/editor.page'; - -describe('Lexicon E2E Configuration Fields', () => { - const constants = require('../../../testConstants.json'); - const loginPage = new BellowsLoginPage(); - const projectsPage = new ProjectsPage(); - const editorPage = new EditorPage(); - const configPage = new ConfigurationPage(); - const util = new Utils(); - - it('setup: login as manager, select test project and first entry', async () => { - await loginPage.loginAsManager(); - await projectsPage.get(); - await projectsPage.clickOnProject(constants.testProjectName); - await editorPage.edit.toListLink.click(); - await editorPage.browse.clickEntryByLexeme(constants.testEntry1.lexeme.th.value); - expect(await editorPage.edit.getFirstLexeme()).toEqual(constants.testEntry1.lexeme.th.value); - }); - - it('check first entry for field order', async () => { - await editorPage.edit.showHiddenFields(); - expect(await editorPage.edit.getFieldLabel(0).getText()).toEqual('Word'); - expect(await editorPage.edit.getFieldLabel(1).getText()).toEqual('Citation Form'); - expect(await editorPage.edit.getFieldLabel(2).getText()).toEqual('Pronunciation'); - }); - - it('check first entry for field-specific input systems', async () => { - const citationFormLabel = 'Citation Form'; - const scientificNameLabel = 'Scientific Name'; - expect(await editorPage.edit.getMultiTextInputSystems(citationFormLabel).count()).toEqual(1); - expect(await editorPage.edit.getMultiTextInputSystems(citationFormLabel).get(0).getText()).toEqual('th'); - expect(await editorPage.edit.getMultiTextInputSystems(scientificNameLabel).count()).toEqual(1); - expect(await editorPage.edit.getMultiTextInputSystems(scientificNameLabel).get(0).getText()).toEqual('en'); - }); - - it('can go to Configuration and select unified Fields tab', async () => { - expect(await configPage.settingsMenuLink.isPresent()).toBe(true); - await configPage.get(); - expect(await configPage.applyButton.isDisplayed()).toBe(true); - expect(await configPage.applyButton.isEnabled()).toBe(false); - await configPage.tabs.unified.click(); - }); - - it('check Apply button is enabled on changes', async () => { - expect(await configPage.applyButton.isEnabled()).toBe(false); - await configPage.unifiedPane.observerCheckbox('English').click(); - expect(await configPage.applyButton.isEnabled()).toBe(true); - await configPage.unifiedPane.observerCheckbox('English').click(); - expect(await configPage.applyButton.isEnabled()).toBe(false); - }); - - describe('Field-Specific Settings', () => { - - it('check sense POS has no field-specific settings', async () => { - const rowLabel = new RegExp('^Part of Speech$'); - expect(await configPage.unifiedPane.fieldSpecificButton(rowLabel).isPresent()).toBe(false); - }); - - it('check sense Pictures "Caption Hidden if Empty" field-specific setting', async () => { - const rowLabel = new RegExp('^Pictures$'); - expect(await configPage.unifiedPane.fieldSpecificButton(rowLabel).isDisplayed()).toBe(true); - expect(await configPage.unifiedPane.fieldSpecificIcon(rowLabel).getAttribute('class')) - .toEqual('fa fa-chevron-down'); - await configPage.unifiedPane.fieldSpecificButton(rowLabel).click(); - expect(await configPage.unifiedPane.fieldSpecificCaptionHiddenIfEmptyCheckbox(rowLabel).isDisplayed()).toBe(true); - await configPage.unifiedPane.fieldSpecificButton(rowLabel).click(); - expect(await configPage.unifiedPane.fieldSpecificCaptionHiddenIfEmptyCheckbox(rowLabel).isPresent()).toBe(false); - }); - - it('check sense field-specific settings', async () => { - const rowLabel = new RegExp('^Scientific Name$'); - expect(await configPage.unifiedPane.fieldSpecificButton(rowLabel).isDisplayed()).toBe(true); - expect(await configPage.unifiedPane.fieldSpecificIcon(rowLabel).getAttribute('class')) - .toEqual('fa fa-chevron-down'); - await configPage.unifiedPane.fieldSpecificButton(rowLabel).click(); - expect(await configPage.unifiedPane.fieldSpecificCaptionHiddenIfEmptyCheckbox(rowLabel).isPresent()).toBe(false); - await configPage.unifiedPane.fieldSpecificButton(rowLabel).click(); - expect(await configPage.unifiedPane.fieldSpecificIcon(rowLabel).getAttribute('class')) - .toEqual('fa fa-chevron-down'); - await configPage.unifiedPane.fieldSpecificButton(rowLabel).click(); - await util.setCheckbox(configPage.unifiedPane.sense.fieldSpecificInputSystemCheckbox(rowLabel, 1), true); - expect(await configPage.unifiedPane.sense.fieldSpecificInputSystemCheckbox(rowLabel, 0).isSelected()).toBe(true); - expect(await configPage.unifiedPane.sense.fieldSpecificInputSystemCheckbox(rowLabel, 1).isSelected()).toBe(true); - }); - - }); - - describe('Reorder Rows', () => { - // Since the drag and drop module inserts items below that row if they are dropped at the middle or below of that - // row, and Utils.simulateDragDrop uses the middle of a row, then set the target to one row above where you want it. - // IJH 2018-03 - - it('can reorder Input System rows', async () => { - expect(await configPage.unifiedPane.inputSystem.rowLabel(0).getText()).toEqual('English'); - expect(await configPage.unifiedPane.inputSystem.rowLabel(1).getText()).toEqual('Thai'); - expect(await configPage.unifiedPane.inputSystem.rowLabel(2).getText()).toEqual('Thai (IPA)'); - await browser.executeScript(Utils.simulateDragDrop, configPage.unifiedPane.inputSystem.rows().get(2).getWebElement(), - configPage.unifiedPane.inputSystem.rows().get(0).getWebElement()); - expect(await configPage.unifiedPane.inputSystem.rowLabel(0).getText()).toEqual('English'); - expect(await configPage.unifiedPane.inputSystem.rowLabel(1).getText()).toEqual('Thai (IPA)'); - expect(await configPage.unifiedPane.inputSystem.rowLabel(2).getText()).toEqual('Thai'); - }); - - it('can reorder Entry rows', async () => { - expect(await configPage.unifiedPane.entry.rowLabel(0).getText()).toEqual('Word'); - expect(await configPage.unifiedPane.entry.rowLabel(1).getText()).toEqual('Citation Form'); - expect(await configPage.unifiedPane.entry.rowLabel(2).getText()).toEqual('Pronunciation'); - await browser.executeScript(Utils.simulateDragDrop, configPage.unifiedPane.entry.rows().get(2).getWebElement(), - configPage.unifiedPane.entry.rows().get(0).getWebElement()); - expect(await configPage.unifiedPane.entry.rowLabel(0).getText()).toEqual('Word'); - expect(await configPage.unifiedPane.entry.rowLabel(1).getText()).toEqual('Pronunciation'); - expect(await configPage.unifiedPane.entry.rowLabel(2).getText()).toEqual('Citation Form'); - }); - - it('check first entry for changed field order', async () => { - expect(await configPage.applyButton.isEnabled()).toBe(true); - await configPage.applyButton.click(); - expect(await configPage.applyButton.isEnabled()).toBe(false); - await Utils.clickBreadcrumb(constants.testProjectName); - await editorPage.edit.toListLink.click(); - await editorPage.browse.clickEntryByLexeme(constants.testEntry1.lexeme.th.value); - await editorPage.edit.showHiddenFields(); - expect(await editorPage.edit.getFieldLabel(0).getText()).toEqual('Word'); - expect(await editorPage.edit.getFieldLabel(1).getText()).toEqual('Pronunciation'); - expect(await editorPage.edit.getFieldLabel(2).getText()).toEqual('Citation Form'); - }); - - it('check first entry for changed field-specific input systems', async () => { - const citationFormLabel = 'Citation Form'; - const scientificNameLabel = 'Scientific Name'; - expect(await editorPage.edit.getMultiTextInputSystems(citationFormLabel).count()).toEqual(1); - expect(await editorPage.edit.getMultiTextInputSystems(citationFormLabel).get(0).getText()).toEqual('th'); - expect(await editorPage.edit.getMultiTextInputSystems(scientificNameLabel).count()).toEqual(2); - expect(await editorPage.edit.getMultiTextInputSystems(scientificNameLabel).get(0).getText()).toEqual('en'); - expect(await editorPage.edit.getMultiTextInputSystems(scientificNameLabel).get(1).getText()).toEqual('th'); - await configPage.get(); - await configPage.tabs.unified.click(); - }); - - it('can restore sense field-specific settings back to how they were', async () => { - const rowLabel = new RegExp('^Scientific Name$'); - const wordChevron = await configPage.unifiedPane.fieldSpecificIcon(rowLabel).getAttribute('class'); - if (wordChevron.includes('fa-chevron-down')) { - await configPage.unifiedPane.fieldSpecificButton(rowLabel).click(); - } - await util.setCheckbox(configPage.unifiedPane.sense.fieldSpecificInputSystemCheckbox(rowLabel, 1), false); - }); - - it('can reorder Entry rows back to how they were', async () => { - await browser.executeScript(Utils.simulateDragDrop, await configPage.unifiedPane.entry.rows().get(2).getWebElement(), - await configPage.unifiedPane.entry.rows().get(0).getWebElement()); - expect(await configPage.unifiedPane.entry.rowLabel(0).getText()).toEqual('Word'); - expect(await configPage.unifiedPane.entry.rowLabel(1).getText()).toEqual('Citation Form'); - expect(await configPage.unifiedPane.entry.rowLabel(2).getText()).toEqual('Pronunciation'); - await configPage.applyButton.click(); - }); - - it('can reorder Sense rows', async () => { - expect(await configPage.unifiedPane.sense.rowLabel(0).getText()).toEqual('Gloss'); - expect(await configPage.unifiedPane.sense.rowLabel(1).getText()).toEqual('Definition'); - expect(await configPage.unifiedPane.sense.rowLabel(2).getText()).toEqual('Pictures'); - await browser.executeScript(Utils.simulateDragDrop, configPage.unifiedPane.sense.rows().get(2).getWebElement(), - configPage.unifiedPane.sense.rows().get(0).getWebElement()); - expect(await configPage.unifiedPane.sense.rowLabel(0).getText()).toEqual('Gloss'); - expect(await configPage.unifiedPane.sense.rowLabel(1).getText()).toEqual('Pictures'); - expect(await configPage.unifiedPane.sense.rowLabel(2).getText()).toEqual('Definition'); - }); - - it('can reorder Example rows', async () => { - expect(await configPage.unifiedPane.example.rowLabel(0).getText()).toEqual('Sentence'); - expect(await configPage.unifiedPane.example.rowLabel(1).getText()).toEqual('Translation'); - expect(await configPage.unifiedPane.example.rowLabel(2).getText()).toEqual('Reference'); - await browser.executeScript(Utils.simulateDragDrop, configPage.unifiedPane.example.rows().get(2).getWebElement(), - configPage.unifiedPane.example.rows().get(0).getWebElement()); - expect(await configPage.unifiedPane.example.rowLabel(0).getText()).toEqual('Sentence'); - expect(await configPage.unifiedPane.example.rowLabel(1).getText()).toEqual('Reference'); - expect(await configPage.unifiedPane.example.rowLabel(2).getText()).toEqual('Translation'); - }); - - }); - - describe('Select All for Columns', () => { - - it('can fully function "Select All" down the Input System observer column', async () => { - const column = 'observer'; - const rowLabel = 'English'; - expect(await configPage.unifiedPane.inputSystem.selectAll.observer.isSelected()).toBe(true); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.inputSystem.columnCheckboxes(column), true)) - .toBe(true); - await util.setCheckbox(configPage.unifiedPane.observerCheckbox(rowLabel), false); - expect(await configPage.unifiedPane.inputSystem.selectAll.observer.isSelected()).toBe(false); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.inputSystem.columnCheckboxes(column), true)) - .toBe(false); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.inputSystem.columnCheckboxes(column), false)) - .toBe(false); - await util.setCheckbox(configPage.unifiedPane.observerCheckbox(rowLabel), true); - expect(await configPage.unifiedPane.inputSystem.selectAll.observer.isSelected()).toBe(true); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.inputSystem.columnCheckboxes(column), true)) - .toBe(true); - await util.setCheckbox(configPage.unifiedPane.inputSystem.selectAll.observer, false); - expect(await configPage.unifiedPane.inputSystem.selectAll.observer.isSelected()).toBe(false); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.inputSystem.columnCheckboxes(column), false)) - .toBe(true); - await util.setCheckbox(configPage.unifiedPane.observerCheckbox(rowLabel), true); - expect(await configPage.unifiedPane.inputSystem.selectAll.observer.isSelected()).toBe(false); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.inputSystem.columnCheckboxes(column), true)) - .toBe(false); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.inputSystem.columnCheckboxes(column), false)) - .toBe(false); - await util.setCheckbox(configPage.unifiedPane.inputSystem.selectAll.observer, true); - expect(await configPage.unifiedPane.inputSystem.selectAll.observer.isSelected()).toBe(true); - }); - - it('can select and de-select all down the Input System commenter column', async () => { - const column = 'commenter'; - expect(await configPage.unifiedPane.inputSystem.selectAll.commenter.isSelected()).toBe(true); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.inputSystem.columnCheckboxes(column), true)) - .toBe(true); - await util.setCheckbox(configPage.unifiedPane.inputSystem.selectAll.commenter, false); - expect(await configPage.unifiedPane.inputSystem.selectAll.commenter.isSelected()).toBe(false); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.inputSystem.columnCheckboxes(column), false)) - .toBe(true); - await util.setCheckbox(configPage.unifiedPane.inputSystem.selectAll.commenter, true); - expect(await configPage.unifiedPane.inputSystem.selectAll.commenter.isSelected()).toBe(true); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.inputSystem.columnCheckboxes(column), true)) - .toBe(true); - }); - - it('can select and de-select all down the Input System contributor column', async () => { - const column = 'contributor'; - expect(await configPage.unifiedPane.inputSystem.selectAll.contributor.isSelected()).toBe(true); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.inputSystem.columnCheckboxes(column), true)) - .toBe(true); - await util.setCheckbox(configPage.unifiedPane.inputSystem.selectAll.contributor, false); - expect(await configPage.unifiedPane.inputSystem.selectAll.contributor.isSelected()).toBe(false); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.inputSystem.columnCheckboxes(column), false)) - .toBe(true); - await util.setCheckbox(configPage.unifiedPane.inputSystem.selectAll.contributor, true); - expect(await configPage.unifiedPane.inputSystem.selectAll.contributor.isSelected()).toBe(true); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.inputSystem.columnCheckboxes(column), true)) - .toBe(true); - }); - - it('can select and de-select all down the Input System manager column', async () => { - const column = 'manager'; - expect(await configPage.unifiedPane.inputSystem.selectAll.manager.isSelected()).toBe(true); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.inputSystem.columnCheckboxes(column), true)) - .toBe(true); - await util.setCheckbox(configPage.unifiedPane.inputSystem.selectAll.manager, false); - expect(await configPage.unifiedPane.inputSystem.selectAll.manager.isSelected()).toBe(false); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.inputSystem.columnCheckboxes(column), false)) - .toBe(true); - await util.setCheckbox(configPage.unifiedPane.inputSystem.selectAll.manager, true); - expect(await configPage.unifiedPane.inputSystem.selectAll.manager.isSelected()).toBe(true); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.inputSystem.columnCheckboxes(column), true)) - .toBe(true); - }); - - it('can de-select and select all down the entry observer column', async () => { - const column = 'observer'; - expect(await configPage.unifiedPane.entry.selectAll.observer.isSelected()).toBe(true); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.entry.columnCheckboxes(column), true)) - .toBe(true); - await util.setCheckbox(configPage.unifiedPane.entry.selectAll.observer, false); - expect(await configPage.unifiedPane.entry.selectAll.observer.isSelected()).toBe(false); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.entry.columnCheckboxes(column), false)) - .toBe(true); - await util.setCheckbox(configPage.unifiedPane.entry.selectAll.observer, true); - expect(await configPage.unifiedPane.entry.selectAll.observer.isSelected()).toBe(true); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.entry.columnCheckboxes(column), true)) - .toBe(true); - }); - - it('can fully function "Select All" down the entry commenter column', async () => { - const column = 'commenter'; - const rowLabel = new RegExp('^Location$'); - expect(await configPage.unifiedPane.entry.selectAll.commenter.isSelected()).toBe(true); - await util.setCheckbox(configPage.unifiedPane.entry.selectAll.commenter, false); - expect(await configPage.unifiedPane.entry.selectAll.commenter.isSelected()).toBe(false); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.entry.columnCheckboxes(column), false)) - .toBe(true); - await util.setCheckbox(configPage.unifiedPane.commenterCheckbox(rowLabel), true); - await util.setCheckbox(configPage.unifiedPane.entry.selectAll.commenter, true); - expect(await configPage.unifiedPane.entry.selectAll.commenter.isSelected()).toBe(true); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.entry.columnCheckboxes(column), true)) - .toBe(true); - await util.setCheckbox(configPage.unifiedPane.commenterCheckbox(rowLabel), false); - expect(await configPage.unifiedPane.entry.selectAll.commenter.isSelected()).toBe(false); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.entry.columnCheckboxes(column), true)) - .toBe(false); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.entry.columnCheckboxes(column), false)) - .toBe(false); - await util.setCheckbox(configPage.unifiedPane.commenterCheckbox(rowLabel), true); - expect(await configPage.unifiedPane.entry.selectAll.commenter.isSelected()).toBe(true); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.entry.columnCheckboxes(column), true)) - .toBe(true); - }); - - it('can de-select and select all down the entry contributor column', async () => { - const column = 'contributor'; - expect(await configPage.unifiedPane.entry.selectAll.contributor.isSelected()).toBe(true); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.entry.columnCheckboxes(column), true)) - .toBe(true); - await util.setCheckbox(configPage.unifiedPane.entry.selectAll.contributor, false); - expect(await configPage.unifiedPane.entry.selectAll.contributor.isSelected()).toBe(false); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.entry.columnCheckboxes(column), false)) - .toBe(true); - await util.setCheckbox(configPage.unifiedPane.entry.selectAll.contributor, true); - expect(await configPage.unifiedPane.entry.selectAll.contributor.isSelected()).toBe(true); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.entry.columnCheckboxes(column), true)) - .toBe(true); - }); - - it('can de-select and select all down the entry manager column', async () => { - const column = 'manager'; - expect(await configPage.unifiedPane.entry.selectAll.manager.isSelected()).toBe(true); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.entry.columnCheckboxes(column), true)) - .toBe(true); - await util.setCheckbox(configPage.unifiedPane.entry.selectAll.manager, false); - expect(await configPage.unifiedPane.entry.selectAll.manager.isSelected()).toBe(false); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.entry.columnCheckboxes(column), false)) - .toBe(true); - await util.setCheckbox(configPage.unifiedPane.entry.selectAll.manager, true); - expect(await configPage.unifiedPane.entry.selectAll.manager.isSelected()).toBe(true); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.entry.columnCheckboxes(column), true)) - .toBe(true); - }); - - it('can de-select and select all down the sense observer column', async () => { - const column = 'observer'; - expect(await configPage.unifiedPane.sense.selectAll.observer.isSelected()).toBe(true); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.sense.columnCheckboxes(column), true)) - .toBe(true); - await util.setCheckbox(configPage.unifiedPane.sense.selectAll.observer, false); - expect(await configPage.unifiedPane.sense.selectAll.observer.isSelected()).toBe(false); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.sense.columnCheckboxes(column), false)) - .toBe(true); - await util.setCheckbox(configPage.unifiedPane.sense.selectAll.observer, true); - expect(await configPage.unifiedPane.sense.selectAll.observer.isSelected()).toBe(true); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.sense.columnCheckboxes(column), true)) - .toBe(true); - }); - - it('can de-select and select all down the sense commenter column', async () => { - const column = 'commenter'; - expect(await configPage.unifiedPane.sense.selectAll.commenter.isSelected()).toBe(true); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.sense.columnCheckboxes(column), true)) - .toBe(true); - await util.setCheckbox(configPage.unifiedPane.sense.selectAll.commenter, false); - expect(await configPage.unifiedPane.sense.selectAll.commenter.isSelected()).toBe(false); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.sense.columnCheckboxes(column), false)) - .toBe(true); - await util.setCheckbox(configPage.unifiedPane.sense.selectAll.commenter, true); - expect(await configPage.unifiedPane.sense.selectAll.commenter.isSelected()).toBe(true); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.sense.columnCheckboxes(column), true)) - .toBe(true); - }); - - it('can fully function "Select All" down the sense contributor column', async () => { - const column = 'contributor'; - const rowLabel = new RegExp('^Scientific Name$'); - expect(await configPage.unifiedPane.sense.selectAll.contributor.isSelected()).toBe(true); - await util.setCheckbox(configPage.unifiedPane.sense.selectAll.contributor, false); - expect(await configPage.unifiedPane.sense.selectAll.contributor.isSelected()).toBe(false); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.sense.columnCheckboxes(column), false)) - .toBe(true); - await util.setCheckbox(configPage.unifiedPane.contributorCheckbox(rowLabel), true); - await util.setCheckbox(configPage.unifiedPane.sense.selectAll.contributor, true); - expect(await configPage.unifiedPane.sense.selectAll.contributor.isSelected()).toBe(true); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.sense.columnCheckboxes(column), true)) - .toBe(true); - await util.setCheckbox(configPage.unifiedPane.contributorCheckbox(rowLabel), false); - expect(await configPage.unifiedPane.sense.selectAll.contributor.isSelected()).toBe(false); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.sense.columnCheckboxes(column), true)) - .toBe(false); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.sense.columnCheckboxes(column), false)) - .toBe(false); - await util.setCheckbox(configPage.unifiedPane.contributorCheckbox(rowLabel), true); - expect(await configPage.unifiedPane.sense.selectAll.contributor.isSelected()).toBe(true); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.sense.columnCheckboxes(column), true)) - .toBe(true); - }); - - it('can de-select and select all down the sense manager column', async () => { - const column = 'manager'; - expect(await configPage.unifiedPane.sense.selectAll.manager.isSelected()).toBe(true); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.sense.columnCheckboxes(column), true)) - .toBe(true); - await util.setCheckbox(configPage.unifiedPane.sense.selectAll.manager, false); - expect(await configPage.unifiedPane.sense.selectAll.manager.isSelected()).toBe(false); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.sense.columnCheckboxes(column), false)) - .toBe(true); - await util.setCheckbox(configPage.unifiedPane.sense.selectAll.manager, true); - expect(await configPage.unifiedPane.sense.selectAll.manager.isSelected()).toBe(true); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.sense.columnCheckboxes(column), true)) - .toBe(true); - }); - - it('can de-select and select all down the example observer column', async () => { - const column = 'observer'; - expect(await configPage.unifiedPane.example.selectAll.observer.isSelected()).toBe(true); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.example.columnCheckboxes(column), true)) - .toBe(true); - await util.setCheckbox(configPage.unifiedPane.example.selectAll.observer, false); - expect(await configPage.unifiedPane.example.selectAll.observer.isSelected()).toBe(false); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.example.columnCheckboxes(column), false)) - .toBe(true); - await util.setCheckbox(configPage.unifiedPane.example.selectAll.observer, true); - expect(await configPage.unifiedPane.example.selectAll.observer.isSelected()).toBe(true); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.example.columnCheckboxes(column), true)) - .toBe(true); - }); - - it('can de-select and select all down the example commenter column', async () => { - const column = 'commenter'; - expect(await configPage.unifiedPane.example.selectAll.commenter.isSelected()).toBe(true); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.example.columnCheckboxes(column), true)) - .toBe(true); - await util.setCheckbox(configPage.unifiedPane.example.selectAll.commenter, false); - expect(await configPage.unifiedPane.example.selectAll.commenter.isSelected()).toBe(false); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.example.columnCheckboxes(column), false)) - .toBe(true); - await util.setCheckbox(configPage.unifiedPane.example.selectAll.commenter, true); - expect(await configPage.unifiedPane.example.selectAll.commenter.isSelected()).toBe(true); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.example.columnCheckboxes(column), true)) - .toBe(true); - }); - - it('can de-select and select all down the example contributor column', async () => { - const column = 'contributor'; - expect(await configPage.unifiedPane.example.selectAll.contributor.isSelected()).toBe(true); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.example.columnCheckboxes(column), true)) - .toBe(true); - await util.setCheckbox(configPage.unifiedPane.example.selectAll.contributor, false); - expect(await configPage.unifiedPane.example.selectAll.contributor.isSelected()).toBe(false); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.example.columnCheckboxes(column), false)) - .toBe(true); - await util.setCheckbox(configPage.unifiedPane.example.selectAll.contributor, true); - expect(await configPage.unifiedPane.example.selectAll.contributor.isSelected()).toBe(true); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.example.columnCheckboxes(column), true)) - .toBe(true); - }); - - it('can fully function "Select All" down the example manager column', async () => { - const column = 'manager'; - const rowLabel = new RegExp('^Reference'); - expect(await configPage.unifiedPane.example.selectAll.manager.isSelected()).toBe(true); - await util.setCheckbox(configPage.unifiedPane.example.selectAll.manager, false); - expect(await configPage.unifiedPane.example.selectAll.manager.isSelected()).toBe(false); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.example.columnCheckboxes(column), false)) - .toBe(true); - await util.setCheckbox(configPage.unifiedPane.managerCheckbox(rowLabel), true); - await util.setCheckbox(configPage.unifiedPane.example.selectAll.manager, true); - expect(await configPage.unifiedPane.example.selectAll.manager.isSelected()).toBe(true); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.example.columnCheckboxes(column), true)) - .toBe(true); - await util.setCheckbox(configPage.unifiedPane.managerCheckbox(rowLabel), false); - expect(await configPage.unifiedPane.example.selectAll.manager.isSelected()).toBe(false); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.example.columnCheckboxes(column), true)) - .toBe(false); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.example.columnCheckboxes(column), false)) - .toBe(false); - await util.setCheckbox(configPage.unifiedPane.managerCheckbox(rowLabel), true); - expect(await configPage.unifiedPane.example.selectAll.manager.isSelected()).toBe(true); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.example.columnCheckboxes(column), true)) - .toBe(true); - }); - - }); - - describe('Select All for Rows', () => { - - it('can fully function "Select All" along an Input System row', async () => { - const rowLabel = new RegExp('^Thai$'); - expect(await configPage.unifiedPane.rowCheckboxes(rowLabel).count()).toEqual(5); - await util.setCheckbox(configPage.unifiedPane.commenterCheckbox(rowLabel), false); - expect(await configPage.unifiedPane.selectRowCheckbox(rowLabel).isSelected()).toBe(false); - await util.setCheckbox(configPage.unifiedPane.selectRowCheckbox(rowLabel), true); - expect(await configPage.unifiedPane.selectRowCheckbox(rowLabel).isSelected()).toBe(true); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.rowCheckboxes(rowLabel), true)).toBe(true); - await util.setCheckbox(configPage.unifiedPane.commenterCheckbox(rowLabel), false); - expect(await configPage.unifiedPane.selectRowCheckbox(rowLabel).isSelected()).toBe(false); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.rowCheckboxes(rowLabel), true)).toBe(false); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.rowCheckboxes(rowLabel), false)).toBe(false); - await util.setCheckbox(configPage.unifiedPane.commenterCheckbox(rowLabel), true); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.rowCheckboxes(rowLabel), true)).toBe(true); - await util.setCheckbox(configPage.unifiedPane.selectRowCheckbox(rowLabel), false); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.rowCheckboxes(rowLabel), false)).toBe(true); - await util.setCheckbox(configPage.unifiedPane.selectRowCheckbox(rowLabel), true); - }); - - it('can fully function "Select All" along an entry row', async () => { - const rowLabel = new RegExp('^Location$'); - expect(await configPage.unifiedPane.rowCheckboxes(rowLabel).count()).toEqual(5); - expect(await configPage.unifiedPane.selectRowCheckbox(rowLabel).isSelected()).toBe(true); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.rowCheckboxes(rowLabel), true)).toBe(true); - await util.setCheckbox(configPage.unifiedPane.contributorCheckbox(rowLabel), false); - expect(await configPage.unifiedPane.selectRowCheckbox(rowLabel).isSelected()).toBe(false); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.rowCheckboxes(rowLabel), true)).toBe(false); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.rowCheckboxes(rowLabel), false)).toBe(false); - await util.setCheckbox(configPage.unifiedPane.contributorCheckbox(rowLabel), true); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.rowCheckboxes(rowLabel), true)).toBe(true); - await util.setCheckbox(configPage.unifiedPane.selectRowCheckbox(rowLabel), false); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.rowCheckboxes(rowLabel), false)).toBe(true); - await util.setCheckbox(configPage.unifiedPane.contributorCheckbox(rowLabel), true); - expect(await configPage.unifiedPane.selectRowCheckbox(rowLabel).isSelected()).toBe(false); - await util.setCheckbox(configPage.unifiedPane.selectRowCheckbox(rowLabel), true); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.rowCheckboxes(rowLabel), true)).toBe(true); - }); - - it('can fully function "Select All" along a sense row', async () => { - const rowLabel = new RegExp('^Scientific Name$'); - expect(await configPage.unifiedPane.rowCheckboxes(rowLabel).count()).toEqual(5); - expect(await configPage.unifiedPane.selectRowCheckbox(rowLabel).isSelected()).toBe(true); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.rowCheckboxes(rowLabel), true)).toBe(true); - await util.setCheckbox(configPage.unifiedPane.managerCheckbox(rowLabel), false); - expect(await configPage.unifiedPane.selectRowCheckbox(rowLabel).isSelected()).toBe(false); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.rowCheckboxes(rowLabel), true)).toBe(false); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.rowCheckboxes(rowLabel), false)).toBe(false); - await util.setCheckbox(configPage.unifiedPane.managerCheckbox(rowLabel), true); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.rowCheckboxes(rowLabel), true)).toBe(true); - await util.setCheckbox(configPage.unifiedPane.selectRowCheckbox(rowLabel), false); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.rowCheckboxes(rowLabel), false)).toBe(true); - await util.setCheckbox(configPage.unifiedPane.managerCheckbox(rowLabel), true); - expect(await configPage.unifiedPane.selectRowCheckbox(rowLabel).isSelected()).toBe(false); - await util.setCheckbox(configPage.unifiedPane.selectRowCheckbox(rowLabel), true); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.rowCheckboxes(rowLabel), true)).toBe(true); - }); - - it('can fully function "Select All" along an example row', async () => { - const rowLabel = new RegExp('^Reference'); - expect(await configPage.unifiedPane.rowCheckboxes(rowLabel).count()).toEqual(5); - expect(await configPage.unifiedPane.selectRowCheckbox(rowLabel).isSelected()).toBe(true); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.rowCheckboxes(rowLabel), true)).toBe(true); - await util.setCheckbox(configPage.unifiedPane.observerCheckbox(rowLabel), false); - expect(await configPage.unifiedPane.selectRowCheckbox(rowLabel).isSelected()).toBe(false); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.rowCheckboxes(rowLabel), true)).toBe(false); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.rowCheckboxes(rowLabel), false)).toBe(false); - await util.setCheckbox(configPage.unifiedPane.observerCheckbox(rowLabel), true); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.rowCheckboxes(rowLabel), true)).toBe(true); - await util.setCheckbox(configPage.unifiedPane.selectRowCheckbox(rowLabel), false); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.rowCheckboxes(rowLabel), false)).toBe(true); - await util.setCheckbox(configPage.unifiedPane.observerCheckbox(rowLabel), true); - expect(await configPage.unifiedPane.selectRowCheckbox(rowLabel).isSelected()).toBe(false); - await util.setCheckbox(configPage.unifiedPane.selectRowCheckbox(rowLabel), true); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.rowCheckboxes(rowLabel), true)).toBe(true); - }); - - }); - - describe('Member-Specific Settings', () => { - let rowNumber = 0; - - it('can count number of Input System rows', async () => { - await configPage.unifiedPane.inputSystem.rows().count().then(count => rowNumber = count); - }); - - it('can add a member-specific user settings', async () => { - expect(await configPage.unifiedPane.inputSystem.selectAll.groups().count()).toEqual(0); - await configPage.unifiedPane.entry.addGroupButton.click(); - await browser.wait(configPage.unifiedPane.addGroupModal.usernameTypeaheadInput.isDisplayed(), 500); - expect(await configPage.unifiedPane.addGroupModal.usernameTypeaheadInput.isDisplayed()).toBe(true); - expect(await configPage.unifiedPane.addGroupModal.usernameTypeaheadResults.count()).toEqual(0); - expect(await configPage.unifiedPane.addGroupModal.addMemberSpecificSettingsButton.isDisplayed()).toBe(true); - await configPage.unifiedPane.addGroupModal.usernameTypeaheadInput.sendKeys('a'); - expect(await configPage.unifiedPane.addGroupModal.usernameTypeaheadResults.count()).toEqual(6); - await configPage.unifiedPane.addGroupModal.usernameTypeaheadInput.clear(); - await configPage.unifiedPane.addGroupModal.usernameTypeaheadInput - .sendKeys(constants.managerUsername + protractor.Key.ENTER); - await configPage.unifiedPane.addGroupModal.addMemberSpecificSettingsButton.click(); - expect(await configPage.unifiedPane.inputSystem.groupColumnCheckboxes(0).count()).toEqual(rowNumber); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.inputSystem.groupColumnCheckboxes(0), true)).toBe(true); - }); - - it('cannot add the same member-specific user settings', async () => { - expect(await configPage.unifiedPane.inputSystem.selectAll.groups().count()).toEqual(1); - await configPage.unifiedPane.entry.addGroupButton.click(); - await browser.wait(configPage.unifiedPane.addGroupModal.usernameTypeaheadInput.isDisplayed(), 500); - expect(await configPage.unifiedPane.addGroupModal.usernameTypeaheadInput.isDisplayed()).toBe(true); - await configPage.unifiedPane.addGroupModal.usernameTypeaheadInput.sendKeys('a'); - expect(await configPage.unifiedPane.addGroupModal.usernameTypeaheadResults.count()).toEqual(5); - await configPage.unifiedPane.addGroupModal.usernameTypeaheadInput.clear(); - await configPage.unifiedPane.addGroupModal.usernameTypeaheadInput - .sendKeys(constants.managerUsername + protractor.Key.ENTER); - expect(await configPage.unifiedPane.addGroupModal.usernameTypeaheadResults.count()).toEqual(0); - await configPage.unifiedPane.addGroupModal.addMemberSpecificSettingsButton.click(); - expect(await configPage.unifiedPane.inputSystem.selectAll.groups().count()).toEqual(1); - }); - - it('can add a second member-specific user settings', async () => { - const rowLabel = 'English'; - await configPage.unifiedPane.entry.addGroupButton.click(); - await browser.wait(configPage.unifiedPane.addGroupModal.usernameTypeaheadInput.isDisplayed(), 500); - expect(await configPage.unifiedPane.addGroupModal.usernameTypeaheadInput.isDisplayed()).toBe(true); - await configPage.unifiedPane.addGroupModal.usernameTypeaheadInput.clear(); - await configPage.unifiedPane.addGroupModal.usernameTypeaheadInput - .sendKeys(constants.memberUsername + protractor.Key.ENTER); - await configPage.unifiedPane.addGroupModal.addMemberSpecificSettingsButton.click(); - expect(await configPage.unifiedPane.inputSystem.selectAll.groups().count()).toEqual(2); - expect(await configPage.unifiedPane.inputSystem.groupColumnCheckboxes(1).count()).toEqual(rowNumber); - expect(await configPage.unifiedPane.rowCheckboxes(rowLabel).count()).toEqual(7); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.inputSystem.groupColumnCheckboxes(1), true)).toBe(true); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.inputSystem.columnCheckboxes('select-row'), true)) - .toBe(true); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.entry.groupColumnCheckboxes(1), true)).toBe(true); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.entry.columnCheckboxes('select-row'), true)) - .toBe(true); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.sense.groupColumnCheckboxes(1), true)).toBe(true); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.sense.columnCheckboxes('select-row'), true)) - .toBe(true); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.example.groupColumnCheckboxes(1), true)).toBe(true); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.example.columnCheckboxes('select-row'), true)) - .toBe(true); - }); - - it('can fully function "Select All" down a Input System member-specific column', async () => { - const columnIndex = 0; - const rowIndex = 0; - const rowLabel = 'English'; - expect(await configPage.unifiedPane.inputSystem.rowLabel(rowIndex).getText()).toEqual(rowLabel); - await util.setCheckbox(configPage.unifiedPane.inputSystem.selectAll.groups().get(columnIndex), true); - await util.setCheckbox(configPage.unifiedPane.inputSystem.groupColumnCheckboxes(columnIndex).get(rowIndex), false); - expect(await configPage.unifiedPane.inputSystem.selectAll.groups().get(columnIndex).isSelected()).toBe(false); - await util.setCheckbox(configPage.unifiedPane.inputSystem.selectAll.groups().get(columnIndex), true); - expect(await configPage.unifiedPane.inputSystem.selectAll.groups().get(columnIndex).isSelected()).toBe(true); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.inputSystem.groupColumnCheckboxes(columnIndex), true)) - .toBe(true); - await util.setCheckbox(configPage.unifiedPane.inputSystem.groupColumnCheckboxes(columnIndex).get(rowIndex), false); - expect(await configPage.unifiedPane.inputSystem.selectAll.groups().get(columnIndex).isSelected()).toBe(false); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.inputSystem.groupColumnCheckboxes(columnIndex), true)) - .toBe(false); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.inputSystem.groupColumnCheckboxes(columnIndex), false)) - .toBe(false); - await util.setCheckbox(configPage.unifiedPane.inputSystem.groupColumnCheckboxes(columnIndex).get(rowIndex), true); - expect(await configPage.unifiedPane.inputSystem.selectAll.groups().get(columnIndex).isSelected()).toBe(true); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.inputSystem.groupColumnCheckboxes(columnIndex), true)) - .toBe(true); - await util.setCheckbox(configPage.unifiedPane.inputSystem.selectAll.groups().get(columnIndex), false); - expect(await configPage.unifiedPane.inputSystem.selectAll.groups().get(columnIndex).isSelected()).toBe(false); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.inputSystem.groupColumnCheckboxes(columnIndex), false)) - .toBe(true); - }); - - it('check cross-over Select All row and column', async () => { - const columnIndex = 0; - const rowIndex = 0; - const rowLabel = 'English'; - await util.setCheckbox(configPage.unifiedPane.inputSystem.selectAll.groups().get(columnIndex), true); - await util.setCheckbox(configPage.unifiedPane.selectRowCheckbox(rowLabel), true); - expect(await configPage.unifiedPane.inputSystem.selectAll.groups().get(columnIndex).isSelected()).toBe(true); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.inputSystem.groupColumnCheckboxes(columnIndex), true)) - .toBe(true); - expect(await configPage.unifiedPane.selectRowCheckbox(rowLabel).isSelected()).toBe(true); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.rowCheckboxes(rowLabel), true)).toBe(true); - await util.setCheckbox(configPage.unifiedPane.inputSystem.groupColumnCheckboxes(columnIndex).get(rowIndex), false); - expect(await configPage.unifiedPane.inputSystem.selectAll.groups().get(columnIndex).isSelected()).toBe(false); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.inputSystem.groupColumnCheckboxes(columnIndex), true)) - .toBe(false); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.inputSystem.groupColumnCheckboxes(columnIndex), false)) - .toBe(false); - expect(await configPage.unifiedPane.selectRowCheckbox(rowLabel).isSelected()).toBe(false); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.rowCheckboxes(rowLabel), true)).toBe(false); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.rowCheckboxes(rowLabel), false)).toBe(false); - await util.setCheckbox(configPage.unifiedPane.inputSystem.selectAll.groups().get(columnIndex), true); - await util.setCheckbox(configPage.unifiedPane.inputSystem.selectAll.groups().get(columnIndex), false); - await util.setCheckbox(configPage.unifiedPane.selectRowCheckbox(rowLabel), true); - await util.setCheckbox(configPage.unifiedPane.selectRowCheckbox(rowLabel), false); - }); - - it('can remove member-specific user settings', async () => { - // Setup, resetting state changed by previous tests - await util.setCheckbox(configPage.unifiedPane.inputSystem.selectAll.observer, true); - await util.setCheckbox(configPage.unifiedPane.inputSystem.selectAll.commenter, true); - await util.setCheckbox(configPage.unifiedPane.inputSystem.selectAll.contributor, true); - await util.setCheckbox(configPage.unifiedPane.inputSystem.selectAll.manager, true); - - // Exercise - await configPage.unifiedPane.entry.removeGroupButton(0).click(); - expect(await configPage.unifiedPane.inputSystem.selectAll.groups().count()).toEqual(1); - await configPage.unifiedPane.entry.removeGroupButton(0).click(); - expect(await configPage.unifiedPane.inputSystem.selectAll.groups().count()).toEqual(0); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.inputSystem.columnCheckboxes('select-row'), true)) - .toBe(true); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.entry.columnCheckboxes('select-row'), true)) - .toBe(true); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.sense.columnCheckboxes('select-row'), true)) - .toBe(true); - expect(await Utils.isAllCheckboxes(configPage.unifiedPane.example.columnCheckboxes('select-row'), true)) - .toBe(true); - }); - - }); - - describe('Custom Fields', () => { - const customDisplayName = 'cust_name'; - let rowNumber = 0; - - it('can discard Configuration changes', async () => { - await Utils.clickBreadcrumb(constants.testProjectName); - await configPage.get(); - await configPage.tabs.unified.click(); - expect(await configPage.applyButton.isEnabled()).toBe(false); - }); - - it('can count number of entry rows', async () => { - await configPage.unifiedPane.entry.rows().count().then(count => rowNumber = count); - }); - - }); - -}); diff --git a/test/app/languageforge/lexicon/settings/config-input-systems.e2e-spec.ts b/test/app/languageforge/lexicon/settings/config-input-systems.e2e-spec.ts deleted file mode 100644 index 96922f1124..0000000000 --- a/test/app/languageforge/lexicon/settings/config-input-systems.e2e-spec.ts +++ /dev/null @@ -1,259 +0,0 @@ -import {protractor} from 'protractor'; - -import {BellowsLoginPage} from '../../../bellows/shared/login.page'; -import {ProjectsPage} from '../../../bellows/shared/projects.page'; -import {Utils} from '../../../bellows/shared/utils'; -import {ConfigurationPage} from '../shared/configuration.page'; - -describe('Lexicon E2E Configuration Input Systems', () => { - const constants = require('../../../testConstants.json'); - const loginPage = new BellowsLoginPage(); - const projectsPage = new ProjectsPage(); - const configPage = new ConfigurationPage(); - const firstLanguage = 'Maori'; - const lastLanguage = 'Rarotongan'; - - it('setup: login as user, select test project, cannot configure', async () => { - await loginPage.loginAsUser(); - await projectsPage.get(); - await projectsPage.clickOnProject(constants.testProjectName); - expect(await configPage.settingsMenuLink.isPresent()).toBe(false); - }); - - it('setup: login as manager, select test project, goto configuration', async () => { - await loginPage.loginAsManager(); - await projectsPage.get(); - await projectsPage.clickOnProject(constants.testProjectName); - expect(await configPage.settingsMenuLink.isPresent()).toBe(true); - await configPage.get(); - expect(await configPage.applyButton.isDisplayed()).toBe(true); - expect(await configPage.applyButton.isEnabled()).toBe(false); - }); - - it('can select Input Systems tab', async () => { - await configPage.tabs.inputSystems.click(); - expect(await configPage.inputSystemsPane.newButton.isDisplayed()).toBe(true); - expect(await configPage.inputSystemsPane.moreButton.isDisplayed()).toBe(true); - }); - - it('can select an existing Input System', async () => { - const language = 'Thai (IPA)'; - const inputSystem = await configPage.inputSystemsPane.getLanguageByName(language); - expect(inputSystem.isDisplayed()).toBe(true); - inputSystem.click(); - expect(await configPage.inputSystemsPane.selectedInputSystem.displayName.getText()).toEqual(language); - expect(await configPage.applyButton.isEnabled()).toBe(false); - }); - - it('cannot change Special for an existing Input System', async () => { - expect(await configPage.inputSystemsPane.selectedInputSystem.tag.getText()).toEqual('th-fonipa'); - expect(await configPage.inputSystemsPane.selectedInputSystem.specialDropdown.isDisplayed()).toBe(true); - expect(await configPage.inputSystemsPane.selectedInputSystem.specialDropdown.isEnabled()).toBe(false); - expect(await configPage.inputSystemsPane.selectedInputSystem.purposeDropdown.isDisplayed()).toBe(true); - expect(await configPage.inputSystemsPane.selectedInputSystem.purposeDropdown.isEnabled()).toBe(false); - }); - - it('cannot add another IPA variation, but can add Voice and Variant', async () => { - await configPage.inputSystemsPane.moreButton.click(); - expect(await configPage.inputSystemsPane.moreButtonGroup.addIpa.getAttribute('class')).toContain('disabled'); - expect(await configPage.inputSystemsPane.moreButtonGroup.addVoice.getAttribute('class')).toContain('disabled'); - expect(await configPage.inputSystemsPane.moreButtonGroup.addVariant.getAttribute('class')) - .not.toContain('disabled'); - }); - - it('cannot remove an existing Input System', async () => { - expect(await configPage.inputSystemsPane.moreButtonGroup.remove.isDisplayed()).toBe(false); - }); - - it('new Input System is selected', async () => { - expect(await configPage.inputSystemsPane.selectedInputSystem.displayName.getText()).toEqual(firstLanguage); - expect(await configPage.inputSystemsPane.selectedInputSystem.tag.getText()).toEqual('mi'); - }); - - it('can change Special to IPA', async () => { - expect(await configPage.inputSystemsPane.selectedInputSystem.specialDropdown.isDisplayed()).toBe(true); - expect(await configPage.inputSystemsPane.selectedInputSystem.purposeDropdown.isDisplayed()).toBe(false); - expect(await configPage.inputSystemsPane.selectedInputSystem.ipaVariantInput.isDisplayed()).toBe(false); - expect(await configPage.inputSystemsPane.selectedInputSystem.voiceVariantInput.isDisplayed()).toBe(false); - expect(await configPage.inputSystemsPane.selectedInputSystem.scriptDropdown.isDisplayed()).toBe(false); - expect(await configPage.inputSystemsPane.selectedInputSystem.regionDropdown.isDisplayed()).toBe(false); - expect(await configPage.inputSystemsPane.selectedInputSystem.variantInput.isDisplayed()).toBe(false); - await Utils.clickDropdownByValue(configPage.inputSystemsPane.selectedInputSystem.specialDropdown, 'IPA transcription'); - expect(await configPage.inputSystemsPane.selectedInputSystem.purposeDropdown.isDisplayed()).toBe(true); - expect(await configPage.inputSystemsPane.selectedInputSystem.ipaVariantInput.isDisplayed()).toBe(true); - expect(await configPage.inputSystemsPane.selectedInputSystem.tag.getText()).toEqual('mi-fonipa'); - }); - - it('can change IPA Variant', async () => { - await configPage.inputSystemsPane.selectedInputSystem.ipaVariantInput.sendKeys('ngati' + protractor.Key.TAB); - expect(await configPage.inputSystemsPane.selectedInputSystem.tag.getText()).toEqual('mi-fonipa-x-ngati'); - await configPage.inputSystemsPane.selectedInputSystem.ipaVariantInput.clear(); - }); - - it('can change IPA Purpose to Etic', async () => { - await Utils.clickDropdownByValue(configPage.inputSystemsPane.selectedInputSystem.purposeDropdown, 'Etic'); - expect(await configPage.inputSystemsPane.selectedInputSystem.tag.getText()).toEqual('mi-fonipa-x-etic'); - }); - - it('can change IPA Variant', async () => { - await configPage.inputSystemsPane.selectedInputSystem.ipaVariantInput.sendKeys('ngati' + protractor.Key.TAB); - expect(await configPage.inputSystemsPane.selectedInputSystem.tag.getText()).toEqual('mi-fonipa-x-etic-ngati'); - await configPage.inputSystemsPane.selectedInputSystem.ipaVariantInput.clear(); - }); - - it('can change IPA Purpose to Emic', async () => { - await Utils.clickDropdownByValue(configPage.inputSystemsPane.selectedInputSystem.purposeDropdown, 'Emic'); - expect(await configPage.inputSystemsPane.selectedInputSystem.tag.getText()).toEqual('mi-fonipa-x-emic'); - }); - - it('can change IPA Variant', async () => { - await configPage.inputSystemsPane.selectedInputSystem.ipaVariantInput.sendKeys('ngati' + protractor.Key.TAB); - expect(await configPage.inputSystemsPane.selectedInputSystem.tag.getText()).toEqual('mi-fonipa-x-emic-ngati'); - await configPage.inputSystemsPane.selectedInputSystem.ipaVariantInput.clear(); - }); - - it('can change IPA Purpose to unspecified', async () => { - await Utils.clickDropdownByValue(configPage.inputSystemsPane.selectedInputSystem.purposeDropdown, 'unspecified'); - expect(await configPage.inputSystemsPane.selectedInputSystem.tag.getText()).toEqual('mi-fonipa'); - }); - - it('can change Special to Voice', async () => { - await Utils.clickDropdownByValue(configPage.inputSystemsPane.selectedInputSystem.specialDropdown, 'Voice'); - expect(await configPage.inputSystemsPane.selectedInputSystem.specialDropdown.isDisplayed()).toBe(true); - expect(await configPage.inputSystemsPane.selectedInputSystem.purposeDropdown.isDisplayed()).toBe(false); - expect(await configPage.inputSystemsPane.selectedInputSystem.ipaVariantInput.isDisplayed()).toBe(false); - expect(await configPage.inputSystemsPane.selectedInputSystem.voiceVariantInput.isDisplayed()).toBe(true); - expect(await configPage.inputSystemsPane.selectedInputSystem.tag.getText()).toEqual('mi-Zxxx-x-audio'); - }); - - it('can change Voice Variant', async () => { - await configPage.inputSystemsPane.selectedInputSystem.voiceVariantInput.sendKeys('ngati' + protractor.Key.TAB); - expect(await configPage.inputSystemsPane.selectedInputSystem.tag.getText()).toEqual('mi-Zxxx-x-audio-ngati'); - await configPage.inputSystemsPane.selectedInputSystem.voiceVariantInput.clear(); - }); - - it('can change Special to Script / Region / Variant', async () => { - await Utils.clickDropdownByValue(configPage.inputSystemsPane.selectedInputSystem.specialDropdown, - 'Script / Region / Variant'); - expect(await configPage.inputSystemsPane.selectedInputSystem.specialDropdown.isDisplayed()).toBe(true); - expect(await configPage.inputSystemsPane.selectedInputSystem.voiceVariantInput.isDisplayed()).toBe(false); - expect(await configPage.inputSystemsPane.selectedInputSystem.scriptDropdown.isDisplayed()).toBe(true); - expect(await configPage.inputSystemsPane.selectedInputSystem.regionDropdown.isDisplayed()).toBe(true); - expect(await configPage.inputSystemsPane.selectedInputSystem.variantInput.isDisplayed()).toBe(true); - expect(await configPage.inputSystemsPane.selectedInputSystem.tag.getText()).toEqual('mi-unspecified'); - }); - - it('can not add unspecified Variant', async () => { - expect(await configPage.noticeList.count()).toBe(0); - await Utils.scrollTop(); - await configPage.applyButton.click(); - expect(await configPage.noticeList.count()).toBe(1); - expect(await configPage.noticeList.get(0).getText()).toContain('Specify at least one Script, Region or Variant'); - await configPage.firstNoticeCloseButton.click(); - }); - - it('can change Script', async () => { - await Utils.clickDropdownByValue(configPage.inputSystemsPane.selectedInputSystem.scriptDropdown, new RegExp('Latin$')); - expect(await configPage.inputSystemsPane.selectedInputSystem.tag.getText()).toEqual('mi-Latn'); - }); - - it('can change Region', async () => { - await Utils.clickDropdownByValue(configPage.inputSystemsPane.selectedInputSystem.regionDropdown, 'Cook Islands'); - expect(await configPage.inputSystemsPane.selectedInputSystem.tag.getText()).toEqual('mi-Latn-CK'); - }); - - it('can change Variant', async () => { - await configPage.inputSystemsPane.selectedInputSystem.variantInput.sendKeys('ngati' + protractor.Key.TAB); - expect(await configPage.inputSystemsPane.selectedInputSystem.tag.getText()).toEqual('mi-Latn-CK-x-ngati'); - }); - - it('can change Special to none', async () => { - await Utils.clickDropdownByValue(configPage.inputSystemsPane.selectedInputSystem.specialDropdown, 'none'); - expect(await configPage.inputSystemsPane.selectedInputSystem.specialDropdown.isDisplayed()).toBe(true); - expect(await configPage.inputSystemsPane.selectedInputSystem.scriptDropdown.isDisplayed()).toBe(false); - expect(await configPage.inputSystemsPane.selectedInputSystem.regionDropdown.isDisplayed()).toBe(false); - expect(await configPage.inputSystemsPane.selectedInputSystem.variantInput.isDisplayed()).toBe(false); - expect(await configPage.inputSystemsPane.selectedInputSystem.tag.getText()).toEqual('mi'); - }); - - it('can add IPA variation', async () => { - await Utils.scrollTop(); - await configPage.inputSystemsPane.moreButton.click(); - await configPage.inputSystemsPane.moreButtonGroup.addIpa.click(); - expect(await configPage.inputSystemsPane.selectedInputSystem.specialDropdown.isDisplayed()).toBe(true); - expect(await configPage.inputSystemsPane.selectedInputSystem.specialDropdown.isEnabled()).toBe(true); - expect(await configPage.inputSystemsPane.selectedInputSystem.purposeDropdown.isDisplayed()).toBe(true); - expect(await configPage.inputSystemsPane.selectedInputSystem.purposeDropdown.isEnabled()).toBe(true); - expect(await configPage.inputSystemsPane.selectedInputSystem.ipaVariantInput.isDisplayed()).toBe(true); - expect(await configPage.inputSystemsPane.selectedInputSystem.tag.getText()).toEqual('mi-fonipa'); - }); - - it('cannot add another IPA variation', async () => { - await configPage.inputSystemsPane.moreButton.click(); - expect(await configPage.inputSystemsPane.moreButtonGroup.addIpa.getAttribute('class')).toContain('disabled'); - }); - - it('can remove IPA variation', async () => { - expect(await configPage.inputSystemsPane.moreButtonGroup.remove.isDisplayed()).toBe(true); - await configPage.inputSystemsPane.moreButtonGroup.remove.click(); - expect(await configPage.inputSystemsPane.selectedInputSystem.tag.getText()).toEqual('en'); - }); - - it('can add Voice variation', async () => { - await configPage.inputSystemsPane.getLanguageByName(firstLanguage).click(); - expect(await configPage.inputSystemsPane.selectedInputSystem.tag.getText()).toEqual('mi'); - await configPage.inputSystemsPane.moreButton.click(); - await configPage.inputSystemsPane.moreButtonGroup.addVoice.click(); - expect(await configPage.inputSystemsPane.selectedInputSystem.specialDropdown.isDisplayed()).toBe(true); - expect(await configPage.inputSystemsPane.selectedInputSystem.specialDropdown.isEnabled()).toBe(true); - expect(await configPage.inputSystemsPane.selectedInputSystem.voiceVariantInput.isDisplayed()).toBe(true); - expect(await configPage.inputSystemsPane.selectedInputSystem.tag.getText()).toEqual('mi-Zxxx-x-audio'); - }); - - it('cannot add another Voice variation', async () => { - await configPage.inputSystemsPane.moreButton.click(); - expect(await configPage.inputSystemsPane.moreButtonGroup.addVoice.getAttribute('class')).toContain('disabled'); - }); - - it('can remove Voice variation', async () => { - expect(await configPage.inputSystemsPane.moreButtonGroup.remove.isDisplayed()).toBe(true); - await configPage.inputSystemsPane.moreButtonGroup.remove.click(); - expect(await configPage.inputSystemsPane.selectedInputSystem.tag.getText()).toEqual('en'); - }); - - it('can add Variant variation', async () => { - await configPage.inputSystemsPane.getLanguageByName(firstLanguage).click(); - expect(await configPage.inputSystemsPane.selectedInputSystem.tag.getText()).toEqual('mi'); - await configPage.inputSystemsPane.moreButton.click(); - await configPage.inputSystemsPane.moreButtonGroup.addVariant.click(); - expect(await configPage.noticeList.count()).toBe(0); - expect(await configPage.inputSystemsPane.selectedInputSystem.specialDropdown.isDisplayed()).toBe(true); - expect(await configPage.inputSystemsPane.selectedInputSystem.specialDropdown.isEnabled()).toBe(true); - expect(await configPage.inputSystemsPane.selectedInputSystem.scriptDropdown.isDisplayed()).toBe(true); - expect(await configPage.inputSystemsPane.selectedInputSystem.scriptDropdown.isEnabled()).toBe(true); - expect(await configPage.inputSystemsPane.selectedInputSystem.regionDropdown.isDisplayed()).toBe(true); - expect(await configPage.inputSystemsPane.selectedInputSystem.regionDropdown.isEnabled()).toBe(true); - expect(await configPage.inputSystemsPane.selectedInputSystem.variantInput.isDisplayed()).toBe(true); - expect(await configPage.inputSystemsPane.selectedInputSystem.variantInput.isEnabled()).toBe(true); - expect(await configPage.inputSystemsPane.selectedInputSystem.tag.getText()).toEqual('mi-unspecified'); - }); - - it('can always add another Variant variation', async () => { - await configPage.inputSystemsPane.moreButton.click(); - expect(await configPage.inputSystemsPane.moreButtonGroup.addVariant.getAttribute('class')).not.toContain('disabled'); - }); - - it('can remove Variant variation', async () => { - expect(await configPage.inputSystemsPane.moreButtonGroup.remove.isDisplayed()).toBe(true); - await configPage.inputSystemsPane.moreButtonGroup.remove.click(); - expect(await configPage.inputSystemsPane.selectedInputSystem.tag.getText()).toEqual('en'); - }); - - it('can save new Input System', async () => { - expect(await configPage.noticeList.count()).toBe(0); - await configPage.applyButton.click(); - expect(await configPage.noticeList.count()).toBe(1); - expect(await configPage.noticeList.get(0).getText()).toContain('Configuration updated successfully'); - }); - -}); diff --git a/test/app/languageforge/lexicon/settings/interface-language.e2e-spec.ts b/test/app/languageforge/lexicon/settings/interface-language.e2e-spec.ts deleted file mode 100644 index 9195b9f671..0000000000 --- a/test/app/languageforge/lexicon/settings/interface-language.e2e-spec.ts +++ /dev/null @@ -1,40 +0,0 @@ -import {BellowsForgotPasswordPage} from '../../../bellows/shared/forgot-password.page'; -import {BellowsLoginPage} from '../../../bellows/shared/login.page'; -import {PageHeader} from '../../../bellows/shared/page-header.element'; - -describe('Interface Language picker (LF only so far)', () => { - const header = new PageHeader(); - - it('should be using English interface for user at Login', async () => { - await BellowsLoginPage.logout(); - await BellowsLoginPage.get(); - expect(await header.language.button.getText()).toEqual('English'); - }); - - it('can change user interface language to French', async () => { - await header.language.button.click(); - await header.language.findItem('Français').click(); - expect(await header.language.button.getText()).toEqual('Français'); - }); - - describe('local storage', async () => { - - it('should still be using French in another page', async () => { - await BellowsForgotPasswordPage.get(); - expect(await header.language.button.getText()).toEqual('Français'); - }); - - it('should still be using French back in the login page', async () => { - await BellowsLoginPage.get(); - expect(await header.language.button.getText()).toEqual('Français'); - }); - - }); - - it('can change user interface language to back English', async () => { - await header.language.button.click(); - await header.language.findItem('English').click(); - expect(await header.language.button.getText()).toEqual('English'); - }); - -}); diff --git a/test/app/languageforge/lexicon/settings/semantic-domains.e2e-spec.ts b/test/app/languageforge/lexicon/settings/semantic-domains.e2e-spec.ts deleted file mode 100644 index 15eb5488bb..0000000000 --- a/test/app/languageforge/lexicon/settings/semantic-domains.e2e-spec.ts +++ /dev/null @@ -1,148 +0,0 @@ -import {browser, ExpectedConditions} from 'protractor'; - -import {BellowsLoginPage} from '../../../bellows/shared/login.page'; -import {PageHeader} from '../../../bellows/shared/page-header.element'; -import {ProjectsPage} from '../../../bellows/shared/projects.page'; -import {Utils} from '../../../bellows/shared/utils'; -import {EditorPage} from '../shared/editor.page'; -import {ProjectSettingsPage} from '../shared/project-settings.page'; - -describe('Lexicon E2E Semantic Domains Lazy Load', () => { - const constants = require('../../../testConstants.json'); - const editorPage = new EditorPage(); - const header = new PageHeader(); - const loginPage = new BellowsLoginPage(); - const projectsPage = new ProjectsPage(); - const projectSettingsPage = new ProjectSettingsPage(); - - const semanticDomain1dot1English = constants.testEntry1.senses[0].semanticDomain.values[0] + ' Sky'; - const semanticDomain1dot1Thai = constants.testEntry1.senses[0].semanticDomain.values[0] + ' ท้องฟ้า'; - - it('should be using English Semantic Domain for manager', async () => { - await loginPage.loginAsManager(); - await projectsPage.get(); - await projectsPage.clickOnProject(constants.testProjectName); - await editorPage.edit.toListLink.click(); - await editorPage.browse.clickEntryByLexeme(constants.testEntry1.lexeme.th.value); - await browser.wait(ExpectedConditions.visibilityOf(await editorPage.edit.fields.last()), Utils.conditionTimeout); - expect(await editorPage.edit.getFirstLexeme()).toEqual(constants.testEntry1.lexeme.th.value); - expect(await editorPage.edit.semanticDomain.values.first().getText()).toEqual(semanticDomain1dot1English); - expect(await header.language.button.getText()).toEqual('English'); - }); - - it('can change Project default language to Thai', async () => { - await projectSettingsPage.getByLink(); - expect(await projectSettingsPage.tabs.project.isDisplayed()).toBe(true); - expect(await projectSettingsPage.projectTab.saveButton.isDisplayed()).toBe(true); - expect(await projectSettingsPage.projectTab.defaultLanguageSelect.isDisplayed()).toBe(true); - expect(await projectSettingsPage.projectTab.defaultLanguageSelected.getText()).toContain('English'); - await projectSettingsPage.projectTab.defaultLanguageSelect.sendKeys('ภาษาไทย'); - await projectSettingsPage.projectTab.saveButton.click(); - expect(await projectSettingsPage.projectTab.defaultLanguageSelected.getText()).toContain('ภาษาไทย'); - expect(await header.language.button.getText()).toEqual('ภาษาไทย'); - }); - - it('should be using Thai Semantic Domain', async () => { - await Utils.clickBreadcrumb(constants.testProjectName); - await editorPage.edit.toListLink.click(); - await editorPage.browse.clickEntryByLexeme(constants.testEntry1.lexeme.th.value); - await browser.wait(ExpectedConditions.visibilityOf(await editorPage.edit.fields.last()), Utils.conditionTimeout); - expect(await editorPage.edit.semanticDomain.values.first().getText()).toEqual(semanticDomain1dot1Thai); - }); - - it('can change Project default language back to English', async () => { - await projectSettingsPage.getByLink(); - expect(await projectSettingsPage.projectTab.defaultLanguageSelected.getText()).toContain('ภาษาไทย'); - await projectSettingsPage.projectTab.defaultLanguageSelect.sendKeys('English'); - await projectSettingsPage.projectTab.saveButton.click(); - expect(await projectSettingsPage.projectTab.defaultLanguageSelected.getText()).toContain('English'); - expect(await header.language.button.getText()).toEqual('English'); - }); - - it('should be using English Semantic Domain', async () => { - await Utils.clickBreadcrumb(constants.testProjectName); - await editorPage.edit.toListLink.click(); - await editorPage.browse.clickEntryByLexeme(constants.testEntry1.lexeme.th.value); - await browser.wait(ExpectedConditions.visibilityOf(await editorPage.edit.fields.last()), Utils.conditionTimeout); - expect(await editorPage.edit.semanticDomain.values.first().getText()).toEqual(semanticDomain1dot1English); - }); - - it('can change Project default language back to Thai', async () => { - await browser.refresh(); - await browser.wait(ExpectedConditions.visibilityOf(editorPage.edit.entryCountElem), Utils.conditionTimeout); - await projectSettingsPage.getByLink(); - expect(await projectSettingsPage.projectTab.defaultLanguageSelected.getText()).toContain('English'); - await projectSettingsPage.projectTab.defaultLanguageSelect.sendKeys('ภาษาไทย'); - await projectSettingsPage.projectTab.saveButton.click(); - expect(await projectSettingsPage.projectTab.defaultLanguageSelected.getText()).toContain('ภาษาไทย'); - }); - - it('should be using Thai Semantic Domain after refresh', async () => { - await Utils.clickBreadcrumb(constants.testProjectName); - await editorPage.edit.toListLink.click(); - await editorPage.browse.clickEntryByLexeme(constants.testEntry1.lexeme.th.value); - expect(await editorPage.edit.semanticDomain.values.first().getText()).toEqual(semanticDomain1dot1Thai); - expect(await editorPage.edit.entryCountElem.isDisplayed()).toBe(true); - await browser.refresh(); - await browser.wait(ExpectedConditions.visibilityOf(editorPage.edit.entryCountElem), Utils.conditionTimeout); - expect(await editorPage.edit.semanticDomain.values.first().getText()).toEqual(semanticDomain1dot1Thai); - }); - - it('can change user interface language', async () => { - expect(await header.language.button.getText()).toEqual('ภาษาไทย'); - await header.language.button.click(); - await header.language.findItem('English').click(); - expect(await header.language.button.getText()).toEqual('English'); - }); - - it('should still have Thai for Project default language', async () => { - await projectSettingsPage.getByLink(); - expect(await projectSettingsPage.projectTab.defaultLanguageSelected.getText()).toContain('ภาษาไทย'); - }); - - it('should be using English Semantic Domain', async () => { - await Utils.clickBreadcrumb(constants.testProjectName); - await editorPage.edit.toListLink.click(); - await editorPage.browse.clickEntryByLexeme(constants.testEntry1.lexeme.th.value); - await browser.wait(ExpectedConditions.visibilityOf(await editorPage.edit.fields.last()), Utils.conditionTimeout); - expect(await editorPage.edit.semanticDomain.values.first().getText()).toEqual(semanticDomain1dot1English); - }); - - it('should be using English Semantic Domain after refresh', async () => { - expect(await editorPage.edit.entryCountElem.isDisplayed()).toBe(true); - await browser.refresh(); - await browser.wait(ExpectedConditions.visibilityOf(editorPage.edit.entryCountElem), Utils.conditionTimeout); - expect(await editorPage.edit.semanticDomain.values.first().getText()).toEqual(semanticDomain1dot1English); - }); - - it('should still have Thai for Project default language', async () => { - await projectSettingsPage.getByLink(); - expect(await projectSettingsPage.projectTab.defaultLanguageSelected.getText()).toContain('ภาษาไทย'); - }); - - it('can change user interface language to English', async () => { - expect(await projectSettingsPage.projectTab.defaultLanguageSelected.getText()).toContain('ภาษาไทย'); - await header.language.button.click(); - await header.language.findItem('English').click(); - expect(await header.language.button.getText()).toEqual('English'); - }); - - it('can change Project default language to match interface language twice', async () => { - await projectSettingsPage.projectTab.defaultLanguageSelect.sendKeys('English'); - await projectSettingsPage.projectTab.saveButton.click(); - expect(await projectSettingsPage.projectTab.defaultLanguageSelected.getText()).toContain('English'); - expect(await header.language.button.getText()).toEqual('English'); - - await projectSettingsPage.projectTab.defaultLanguageSelect.sendKeys('ภาษาไทย'); - await projectSettingsPage.projectTab.saveButton.click(); - expect(await projectSettingsPage.projectTab.defaultLanguageSelected.getText()).toContain('ภาษาไทย'); - expect(await header.language.button.getText()).toEqual('ภาษาไทย'); - }); - - it('can change user interface language to back English', async () => { - await header.language.button.click(); - await header.language.findItem('English').click(); - expect(await header.language.button.getText()).toEqual('English'); - }); - -}); diff --git a/test/app/languageforge/lexicon/shared/configuration.page.ts b/test/app/languageforge/lexicon/shared/configuration.page.ts deleted file mode 100644 index eea4791628..0000000000 --- a/test/app/languageforge/lexicon/shared/configuration.page.ts +++ /dev/null @@ -1,257 +0,0 @@ -import {browser, by, element} from 'protractor'; - -import {Utils} from '../../../bellows/shared/utils'; -import {LexModals} from './lex-modals.util'; - -export class ConfigurationPage { - modal = new LexModals(); - - noticeList = element.all(by.repeater('notice in $ctrl.notices()')); - firstNoticeCloseButton = this.noticeList.first().element(by.className('close')); - - settingsMenuLink = element(by.id('settings-dropdown-button')); - configurationLink = element(by.id('dropdown-configuration')); - - async get() { - await Utils.scrollTop(); - await this.settingsMenuLink.click(); - return this.configurationLink.click(); - } - - applyButton = element(by.id('configuration-apply-btn')); - - private readonly activePane = element(by.css('div.tab-pane.active')); - - // These will be updated once the pui-tab is updated to support unique id - tabs = { - unified: element(by.linkText('Fields')), - inputSystems: element(by.linkText('Input Systems')), - optionlists: element(by.linkText('Option Lists')) - }; - - unifiedPane = { - inputSystem: { - addGroupButton: this.activePane.element(by.id('add-group-input-system-btn')), - selectAll: { - observer: this.activePane.element(by.id('input-system-select-all-observer-checkbox')), - commenter: this.activePane.element(by.id('input-system-select-all-commenter-checkbox')), - contributor: this.activePane.element(by.id('input-system-select-all-contributor-checkbox')), - manager: this.activePane.element(by.id('input-system-select-all-manager-checkbox')), - groups: () => { - return this.activePane.all(by.className('input-system-select-all-group-checkbox')); - } - }, - rows: () => { - return this.activePane.all(by.repeater('inputSystem in $ctrl.unifiedViewModel.inputSystems.settings')); - }, - rowLabel: (rowIndex: number) => { - return this.unifiedPane.inputSystem.rows().get(rowIndex).element(by.className('row-label')); - }, - columnCheckboxes: (column: string) => { - // column values: 'select-row', 'observer', 'commenter', 'contributor', 'manager'. - return this.unifiedPane.inputSystem.rows().all(by.className(column + '-checkbox')); - }, - groupColumnCheckboxes: (groupIndex: number) => { - return this.unifiedPane.inputSystem.rows().all(by.className('checkbox-group-' + groupIndex)); - }, - removeGroupButton: (groupIndex: number) => { - return this.activePane.element(by.id('table-header')) - .element(by.className('remove-button-group-' + groupIndex)); - } - }, - entry: { - addGroupButton: this.activePane.element(by.id('add-group-entry-btn')), - selectAll: { - observer: this.activePane.element(by.id('entry-select-all-observer-checkbox')), - commenter: this.activePane.element(by.id('entry-select-all-commenter-checkbox')), - contributor: this.activePane.element(by.id('entry-select-all-contributor-checkbox')), - manager: this.activePane.element(by.id('entry-select-all-manager-checkbox')), - groups: () => { - return this.activePane.all(by.className('entry-select-all-group-checkbox')); - } - }, - rows: () => { - return this.activePane.all(by.repeater('entryField in $ctrl.unifiedViewModel.entryFields.settings')); - }, - rowLabel: (rowIndex: number) => { - return this.unifiedPane.entry.rows().get(rowIndex).element(by.className('row-label')); - }, - rowLabelCustomInput: (rowIndex: number) => { - return this.unifiedPane.entry.rowLabel(rowIndex).element(by.tagName('input')); - }, - columnCheckboxes: (column: string) => { - // column values: 'select-row', 'observer', 'commenter', 'contributor', 'manager'. - return this.unifiedPane.entry.rows().all(by.className(column + '-checkbox')); - }, - groupColumnCheckboxes: (groupIndex: number) => { - return this.unifiedPane.entry.rows().all(by.className('checkbox-group-' + groupIndex)); - }, - removeGroupButton: (groupIndex: number) => { - return this.activePane.element(by.id('entry-header')) - .element(by.className('remove-button-group-' + groupIndex)); - }, - fieldSpecificInputSystemCheckbox: (label: string|RegExp, inputSystemSelector: string|number) => { - const candidates = this.getRowByLabel(label).element(by.xpath('..')).element(by.className('field-specific-input-systems')) - .all(by.repeater('inputSystemSettings in entryField.inputSystems')); - const filtered = typeof inputSystemSelector === 'number' ? candidates.get(inputSystemSelector) : - candidates.filter(elem => elem.getText().then(text => text.includes(inputSystemSelector))).first(); - return filtered.element(by.className('checkbox')); - } - }, - sense: { - addGroupButton: this.activePane.element(by.id('add-group-sense-btn')), - selectAll: { - observer: this.activePane.element(by.id('sense-select-all-observer-checkbox')), - commenter: this.activePane.element(by.id('sense-select-all-commenter-checkbox')), - contributor: this.activePane.element(by.id('sense-select-all-contributor-checkbox')), - manager: this.activePane.element(by.id('sense-select-all-manager-checkbox')), - groups: () => { - return this.activePane.all(by.className('sense-select-all-group-checkbox')); - } - }, - rows: () => { - return this.activePane.all(by.repeater('senseField in $ctrl.unifiedViewModel.senseFields.settings')); - }, - rowLabel: (rowIndex: number) => { - return this.unifiedPane.sense.rows().get(rowIndex).element(by.className('row-label')); - }, - rowLabelCustomInput: (rowIndex: number) => { - return this.unifiedPane.sense.rowLabel(rowIndex).element(by.tagName('input')); - }, - columnCheckboxes: (column: string) => { - // column values: 'select-row', 'observer', 'commenter', 'contributor', 'manager'. - return this.unifiedPane.sense.rows().all(by.className(column + '-checkbox')); - }, - groupColumnCheckboxes: (groupIndex: number) => { - return this.unifiedPane.sense.rows().all(by.className('checkbox-group-' + groupIndex)); - }, - removeGroupButton: (groupIndex: number) => { - return this.activePane.element(by.id('sense-header')) - .element(by.className('remove-button-group-' + groupIndex)); - }, - fieldSpecificInputSystemCheckbox: (label: string|RegExp, inputSystemSelector: string|number) => { - const candidates = this.getRowByLabel(label).element(by.xpath('..')).element(by.className('field-specific-input-systems')) - .all(by.repeater('inputSystemSettings in senseField.inputSystems')); - const filtered = typeof inputSystemSelector === 'number' ? candidates.get(inputSystemSelector) : - candidates.filter(elem => elem.getText().then(text => text.includes(inputSystemSelector))).first(); - return filtered.element(by.className('checkbox')); - } - }, - example: { - addGroupButton: this.activePane.element(by.id('add-group-example-btn')), - selectAll: { - observer: this.activePane.element(by.id('example-select-all-observer-checkbox')), - commenter: this.activePane.element(by.id('example-select-all-commenter-checkbox')), - contributor: this.activePane.element(by.id('example-select-all-contributor-checkbox')), - manager: this.activePane.element(by.id('example-select-all-manager-checkbox')), - groups: () => { - return this.activePane.all(by.className('example-select-all-group-checkbox')); - } - }, - rows: () => { - return this.activePane.all(by.repeater('exampleField in $ctrl.unifiedViewModel.exampleFields.settings')); - }, - rowLabel: (rowIndex: number) => { - return this.unifiedPane.example.rows().get(rowIndex).element(by.className('row-label')); - }, - rowLabelCustomInput: (rowIndex: number) => { - return this.unifiedPane.example.rowLabel(rowIndex).element(by.tagName('input')); - }, - columnCheckboxes: (column: string) => { - // column values: 'select-row', 'observer', 'commenter', 'contributor', 'manager'. - return this.unifiedPane.example.rows().all(by.className(column + '-checkbox')); - }, - groupColumnCheckboxes: (groupIndex: number) => { - return this.unifiedPane.example.rows().all(by.className('checkbox-group-' + groupIndex)); - }, - removeGroupButton: (groupIndex: number) => { - return this.activePane.element(by.id('example-header')) - .element(by.className('remove-button-group-' + groupIndex)); - }, - fieldSpecificInputSystemCheckbox: (label: string|RegExp, inputSystemSelector: string|number) => { - const candidates = this.getRowByLabel(label).element(by.xpath('..')).element(by.className('field-specific-input-systems')) - .all(by.repeater('inputSystemSettings in exampleField.inputSystems')); - const filtered = typeof inputSystemSelector === 'number' ? candidates.get(inputSystemSelector) : - candidates.filter(elem => elem.getText().then(text => text.includes(inputSystemSelector))).first(); - return filtered.element(by.className('checkbox')); - } - }, - hiddenIfEmptyCheckbox: (label: string|RegExp) => { - return this.getRowByLabel(label).element(by.className('hidden-if-empty-checkbox')); - }, - selectRowCheckbox: (label: string|RegExp) => { - return this.getRowByLabel(label).element(by.className('select-row-checkbox')); - }, - observerCheckbox: (label: string|RegExp) => { - return this.getRowByLabel(label).element(by.className('observer-checkbox')); - }, - commenterCheckbox: (label: string|RegExp) => { - return this.getRowByLabel(label).element(by.className('commenter-checkbox')); - }, - contributorCheckbox: (label: string|RegExp) => { - return this.getRowByLabel(label).element(by.className('contributor-checkbox')); - }, - managerCheckbox: (label: string|RegExp) => { - return this.getRowByLabel(label).element(by.className('manager-checkbox')); - }, - rowCheckboxes: (label: string|RegExp) => { - return this.getRowByLabel(label).all(by.css('input[type="checkbox"]:not(.hidden-if-empty-checkbox)')); - }, - fieldSpecificButton: (label: string|RegExp) => { - return this.getRowByLabel(label).element(by.className('field-specific-btn')); - }, - fieldSpecificIcon: (label: string|RegExp) => { - return this.unifiedPane.fieldSpecificButton(label).element(by.tagName('i')); - }, - fieldSpecificCaptionHiddenIfEmptyCheckbox: (label: string|RegExp) => { - return this.getRowByLabel(label).all(by.xpath('following-sibling::tr')).get(0) - .element(by.className('caption-hidden-if-empty-checkbox')); - }, - addGroupModal: { - usernameTypeaheadInput: element(by.id('typeaheadInput')), - usernameTypeaheadResults: element.all(by.repeater('user in $ctrl.typeahead.users')), - addMemberSpecificSettingsButton: element(by.id('add-member-specific-settings-btn')) - } - }; - - inputSystemsPane = { - newButton: this.activePane.element(by.id('configuration-new-btn')), - moreButton: this.activePane.element(by.id('configuration-dropdown-btn')), - moreButtonGroup: { - addIpa: this.activePane.element(by.id('configuration-add-ipa-btn')), - addVoice: this.activePane.element(by.id('configuration-add-voice-btn')), - addVariant: this.activePane.element(by.id('configuration-add-variant-btn')), - remove: this.activePane.element(by.id('configuration-remove-btn')), - // tslint:disable-next-line:max-line-length - // see http://stackoverflow.com/questions/25553057/making-protractor-wait-until-a-ui-boostrap-modal-box-has-disappeared-with-cucum - newButtonClick: async () => { - await this.inputSystemsPane.newButton.click(); - return browser.executeScript('$(\'.modal\').removeClass(\'fade\');'); - } - }, - getLanguageByName: (languageName: string) => - element(by.css('div.tab-pane.active div.col-md-3 dl.picklists')) - .element(by.cssContainingText('div[data-ng-repeat] span', languageName)), - - selectedInputSystem: { - displayName: this.activePane.element(by.id('languageDisplayName')), - tag: this.activePane.element(by.binding( - '$ctrl.iscInputSystemViewModels[$ctrl.selectedInputSystemId].inputSystem.tag')), - abbreviationInput: this.activePane.element(by.model( - '$ctrl.iscInputSystemViewModels[$ctrl.selectedInputSystemId].inputSystem.abbreviation')), - rightToLeftCheckbox: this.activePane.element(by.model( - '$ctrl.iscInputSystemViewModels[$ctrl.selectedInputSystemId].inputSystem.isRightToLeft')), - specialDropdown: this.activePane.element(by.id('special')), - purposeDropdown: this.activePane.element(by.id('purpose')), - ipaVariantInput: this.activePane.element(by.id('ipaVariant')), - voiceVariantInput: this.activePane.element(by.id('voiceVariant')), - scriptDropdown: this.activePane.element(by.id('script')), - regionDropdown: this.activePane.element(by.id('region')), - variantInput: this.activePane.element(by.id('variant')) - } - }; - - private getRowByLabel(label: string|RegExp) { - return this.activePane.element(by.cssContainingText('td', label)).element(by.xpath('..')); - } -} diff --git a/test/app/languageforge/lexicon/shared/editor.page.ts b/test/app/languageforge/lexicon/shared/editor.page.ts deleted file mode 100644 index ba606a9b5a..0000000000 --- a/test/app/languageforge/lexicon/shared/editor.page.ts +++ /dev/null @@ -1,476 +0,0 @@ -import {browser, by, element, ExpectedConditions} from 'protractor'; - -import {ElementArrayFinder, ElementFinder} from 'protractor/built/element'; -import {MockUploadElement} from '../../../bellows/shared/mock-upload.element'; -import {Utils} from '../../../bellows/shared/utils'; -import {EditorUtil} from './editor.util'; -import {LexModals} from './lex-modals.util'; - -export class EditorPage { - private readonly mockUpload = new MockUploadElement(); - private readonly editorUtil = new EditorUtil(); - - modal = new LexModals(); - - static get(projectId: string, entryId: string) { - let extra = projectId ? ('/' + projectId) : ''; - extra += (projectId && entryId) ? ('#!/editor/entry/' + entryId) : ''; - return browser.get(browser.baseUrl + '/app/lexicon' + extra); - } - - static getProjectIdFromUrl() { - return browser.getCurrentUrl().then(url => { - const match = url.match(/\/app\/lexicon\/([0-9a-z]{24})/); - let projectId = ''; - if (match) { - projectId = match[1]; - } - - return projectId; - }); - } - - static getEntryIdFromUrl() { - return browser.getCurrentUrl().then(url => { - const match = url.match(/\/editor\/entry\/([0-9a-z_]{6,24})/); - let entryId = ''; - if (match) { - entryId = match[1]; - } - - return entryId; - }); - } - - noticeList = element.all(by.repeater('notice in $ctrl.notices()')); - firstNoticeCloseButton = this.noticeList.first().element(by.partialButtonText('×')); - - browseDiv = element(by.id('lexAppListView')); - editDiv = element(by.id('lexAppEditView')); - appDiv = element(by.id('lexAppContainer')); - editToolbarDiv = element(by.id('lexAppToolbar')); - commentDiv = element(by.id('lexAppCommentView')); - - // --- Browse view --- - browse = { - // Top row UI elements - noEntriesElem: this.browseDiv.element(by.id('noEntries')), - noEntriesNewWordBtn: element(by.id('noEntriesNewWord')), - newWordBtn: element(by.id('newWord')), - entryCountElem: this.browseDiv.element(by.id('totalNumberOfEntries')), - getEntryCount: async () => { - // assumption is entry count > 0 - await browser.wait(ExpectedConditions.visibilityOf(this.browse.entryCountElem), Utils.conditionTimeout); - return this.browse.entryCountElem.getText().then((s: string) => - parseInt(/(\d+)$/.exec(s)[1], 10) - ); - }, - - // Search/filter - search: { - input: this.browseDiv.element(by.id('editor-list-search-entries')), - clearBtn: this.browseDiv.element(by.className('clear-search-button')), - getMatchCount: () => { - // Inside this function, "this" == EditorPage.browse.search - return this.browse.entryCountElem.getText().then((s: string) => - parseInt(/^(\d+)/.exec(s)[1], 10) - ); - } - }, - - // Entries list (main body of view) - entriesList: this.browseDiv.all(by.repeater('entry in $ctrl.visibleEntries track by entry.id')), - clickEntryByLexeme: async (lexeme: string) => { - await browser.wait(ExpectedConditions.visibilityOf( - element(by.id('lexAppListView'))), Utils.conditionTimeout); - const elements = await this.browse.entriesList.filter(async (row: ElementFinder) => { - const elem = row.element(by.binding('entry.word')); - - // fix problem with protractor not scrolling to element before click - await browser.driver.executeScript('arguments[0].scrollIntoView();', elem.getWebElement()); - return elem.getText().then((word: string) => - (word.indexOf(lexeme) > -1) - ); - }) - if (elements.length > 0) { - return elements[0].click(); - } else { - throw new Error(`No entry found by lexeme ${lexeme}`); - - }; - } - }; - - // --- Edit view --- - edit = { - // Top row UI elements - toListLink: this.editToolbarDiv.element(by.id('toListLink')), - saveBtn: this.editToolbarDiv.element(by.id('saveEntryBtn')), - toggleHiddenFieldsBtn: this.editToolbarDiv.element(by.id('toggleHiddenFieldsBtn')), - toCommentsLink: this.editToolbarDiv.element(by.id('toCommentsLink')), - - // Show/Hide fields functions - toggleHiddenFieldsBtnText: { - show: 'Show Extra Fields', - hide: 'Hide Extra Fields' - }, - showHiddenFields: () => { - // Only click the button if it will result in fields being shown - return this.edit.toggleHiddenFieldsBtn.getText().then(async (text: string) => { - if (text === this.edit.toggleHiddenFieldsBtnText.show) { - await Utils.scrollTop(); - return this.edit.toggleHiddenFieldsBtn.click(); - } - }); - }, - - hideHiddenFields: () => { - // Only click the button if it will result in fields being hidden - return this.edit.toggleHiddenFieldsBtn.getText().then(async (text: string) => { - if (text === this.edit.toggleHiddenFieldsBtnText.hide) { - await Utils.scrollTop(); - return this.edit.toggleHiddenFieldsBtn.click(); - } - }); - }, - - // Left sidebar UI elements - newWordBtn: this.editDiv.element(by.id('editorNewWordBtn')), - entryCountElem: this.editDiv.element(by.id('totalNumberOfEntries')), - getEntryCount: () => { - return this.edit.entryCountElem.getText().then((s: string) => - parseInt(s, 10) - ); - }, - - entriesList: this.editDiv.all(by.repeater('entry in $ctrl.visibleEntries')), - clickEntryByLexeme: (lexeme: string) => { - const div = this.editDiv.element(by.id('compactEntryListContainer')); - return div.element(by.cssContainingText('.listItemPrimary', - lexeme)); - }, - - findEntryByDefinition: (definition: string) => { - const div = this.editDiv.element(by.id('compactEntryListContainer')); - return div.element(by.cssContainingText('.listItemSecondary', - definition)); - }, - - search: { - input: this.editDiv.element(by.id('editor-entry-search-entries')), - clearBtn: this.editDiv.element(by.className('clear-search-button')), - entryCountElem: this.editDiv.element(by.id('totalNumberOfEntries')), - getMatchCount: () => { - // Inside this function, "this" == EditorPage.edit.search - return this.edit.search.entryCountElem.getText().then((s: string) => - parseInt(/^(\d+)/.exec(s)[1], 10) - ); - } - }, - - // Top-row - renderedDiv: this.appDiv.element(by.className('dc-rendered-entryContainer')), - actionMenu: this.editDiv.element(by.css('.entry-card .card-header .ellipsis-menu-toggle')), - deleteMenuItem: this.editDiv.element(by.css('.entry-card .card-header .dropdown-menu .dropdown-item')), - - // Helper functions for retrieving various field values - fields: this.editDiv.all(by.repeater('fieldName in $ctrl.config.fieldOrder')), - getLexemes: () => { - - // Returns lexemes in the format [{wsid: 'en', value: 'word'}, {wsid: - // 'de', value: 'Wort'}] - const lexeme = this.edit.fields.get(0); - return this.editorUtil.dcMultitextToArray(lexeme); - }, - - getLexemesAsObject: () => { - - // Returns lexemes in the format [{en: 'word', de: 'Wort'}] - const lexeme = this.edit.fields.get(0); - return this.editorUtil.dcMultitextToObject(lexeme); - }, - - getFirstLexeme: async () => { - await browser.wait(ExpectedConditions.visibilityOf(this.edit.fields.get(0)), Utils.conditionTimeout); - - // Returns the first (topmost) lexeme regardless of its wsid - const lexeme = this.edit.fields.get(0); - return this.editorUtil.dcMultitextToFirstValue(lexeme); - }, - - getLexemeByWsid: (searchWsid: string) => { - const lexeme = this.edit.fields.get(0); - return this.editorUtil.dcMultitextToObject(lexeme).then((lexemes: string) => - lexemes[searchWsid] - ); - }, - - getFieldLabel: (fieldIndex: number) => { - return this.edit.fields.get(fieldIndex).all(by.tagName('label')).get(0); - }, - - audio: { - players: (searchLabel: string) => { - return EditorUtil.getOneField(searchLabel).all(by.css('.player a')); - }, - - playerIcons: (searchLabel: string) => { - return EditorUtil.getOneField(searchLabel).all(by.css('.player a > i')); - }, - - moreControls: (searchLabel: string) => { - return EditorUtil.getOneField(searchLabel).all(by.css('.dc-audio a.dropdown-toggle')); - }, - - moreGroups: (searchLabel: string, index: number) => { - const allMoreGroups = EditorUtil.getOneField(searchLabel).all(by.css('.dc-audio .dropdown')); - if (index !== undefined) { - if (index < 0) index = 0; - return allMoreGroups.get(index); - } - - return allMoreGroups; - }, - - moreDownload: (searchLabel: string, index: number) => { - return this.edit.audio.moreGroups(searchLabel, index).element(by.className('dc-audio-download')); - }, - - moreDelete: (searchLabel: string, index: number) => { - return this.edit.audio.moreGroups(searchLabel, index).element(by.className('dc-audio-delete')); - }, - - moreUpload: (searchLabel: string, index: number) => { - return this.edit.audio.moreGroups(searchLabel, index).element(by.className('dc-audio-upload')); - }, - - uploadButtons: (searchLabel: string) => { - return EditorUtil.getOneField(searchLabel).all(by.css('.dc-audio .upload-audio')); - }, - - uploadDropBoxes: (searchLabel: string) => { - return EditorUtil.getOneField(searchLabel).all(by.css('.drop-box')); - }, - - uploadCancelButtons: (searchLabel: string) => { - return EditorUtil.getOneField(searchLabel).all(by.css('.dc-audio i.fa-times')); - }, - - downloadButtons: (searchLabel: string) => { - return EditorUtil.getOneField(searchLabel).all(by.css('.dc-audio a.buttonAppend')); - }, - - control: (searchLabel: string, index: number) => { - const mockUploadElement = EditorUtil.getOneField(searchLabel).all(by.css('.dc-audio')).get(index); - mockUploadElement.mockUpload = this.mockUpload; - return mockUploadElement; - } - }, - - senses: element.all(by.css('dc-sense')), - - sense: { - actionMenus: this.editDiv.all(by.css('dc-sense .ellipsis-menu-toggle')), - deleteSense: this.editDiv.all(by.css('dc-sense .ellipsis-menu-toggle ~ .dropdown-menu fa-trash')), - moveUp: this.editDiv.all(by.css('dc-sense .ellipsis-menu-toggle ~ .dropdown-menu .fa-arrow-up')), - moveDown: this.editDiv.all(by.css('dc-sense .ellipsis-menu-toggle ~ .dropdown-menu .fa-arrow-down')) - }, - - pictures: { - list: EditorUtil.getOneField('Pictures'), - images: EditorUtil.getOneField('Pictures').all(by.css('img')), - captions: EditorUtil.getOneField('Pictures') - .all(by.css('.input-group > .dc-text textarea')), - removeImages: EditorUtil.getOneField('Pictures').all(by.className('fa-trash')), - getFileName: (index: number) => { - return this.editorUtil.getOneFieldValue('Pictures').then((pictures: any) => - pictures[index].fileName - ); - }, - - getCaption: (index: number) => { - return this.editorUtil.getOneFieldValue('Pictures').then((pictures: any) => - pictures[index].caption - ); - }, - - addPictureLink: element(by.id('dc-picture-add-btn')), - addDropBox: EditorUtil.getOneField('Pictures').element(by.css('.drop-box')), - addCancelButton: element(by.id('addCancel')) - }, - - semanticDomain: { - values: EditorUtil.getOneField('Semantic Domain').all(by.className('dc-semanticdomain-value')) - }, - - getMultiTextInputs: (searchLabel: string) => { - return EditorUtil.getOneField(searchLabel) - .all(by.css('.input-group > .dc-text textarea')); - }, - - getMultiTextInputSystems: (searchLabel: string) => { - return EditorUtil.getOneField(searchLabel).all(by.css('.input-group > span.wsid')); - }, - - selectElement: this.editorUtil.selectElement, - getFields: EditorUtil.getFields, - getOneField: EditorUtil.getOneField, - getFieldValues: this.editorUtil.getFieldValues, - getOneFieldValue: this.editorUtil.getOneFieldValue - }; - - // --- Comment view --- - comment = { - toEditLink: element(by.id('toEditLink')), - - bubbles: { - first: element.all(by.css('.dc-entry .commentBubble')).get(1), - second: element.all(by.css('.dc-entry .dc-sense .commentBubble')).get(1) - }, - - // Top-row UI elements - renderedDiv: this.commentDiv.element(by.css('dc-rendered')), - filter: { - byTextElem: this.commentDiv.element(by.model('$ctrl.commentFilter.text')), - byStatusElem: this.commentDiv.element(by.model('$ctrl.commentFilter.status')), - clearElem: this.commentDiv.element(by.css('[title="Clear Filter] > i.fa-times')), - byText: (textToFilterBy: string) => { - this.comment.filter.byTextElem.sendKeys(textToFilterBy); - }, - - byStatus: (statusToFilterBy: string) => { - Utils.clickDropdownByValue(this.comment.filter.byStatusElem, statusToFilterBy); - }, - - clearByText: () => { - this.comment.filter.clearElem.click(); - }, - - clearByStatus: () => { - this.comment.filter.byStatus('Show All'); - } - }, - - // Left half of page: entry (with clickable elements) - entry: { - // We can just reuse the functions from dbeUtil, since they default to - // using element(by.css('dc-entry')) as their root element. - getFields: EditorUtil.getFields, - getOneField: EditorUtil.getOneField, - getFieldValues: this.editorUtil.getFieldValues, - getOneFieldValue: this.editorUtil.getOneFieldValue, - getOneFieldAllInputSystems: (searchLabel: string, idx: number = 0, - rootElem: ElementFinder = element(by.className('dc-entry'))) => { - return EditorUtil.getOneField(searchLabel, idx, rootElem).all(by.css('span.wsid')); - } - }, - - // Right half of page: comments - newComment: { - textarea: element(by.id('comment-panel-textarea')), - postBtn: element(by.id('comment-panel-post-button')) - }, - commentsList: this.commentDiv.all(by.repeater('comment in $ctrl.currentEntryCommentsFiltered')), - getComment: (commentNum: number) => { - return EditorPage.getComment(this.comment.commentsList, commentNum); - } - }; - - // Allow access by either name - comments = this.comment; - - // Gets a specific comment from the list and returns its parts (via - // partsOfComment() below) - // the specified comment. Usage example: - // expect(this.comments.getComment(0).regarding.inputSystem).toBe("th") - // commentNum can be -1 to get the last comment, any other number is a 0-based - // index - static getComment(commentsList: ElementArrayFinder, commentNum: number = 0) { - const comment = (commentNum === -1 ? commentsList.last() : commentsList.get(commentNum)); - return EditorPage.partsOfComment(comment); - } - - // Like getComment, gets a specific reply from the list and returns its parts - // (via partsOfReply() below) - // replyNum can be -1 to get the last reply, any other number is a 0-based - // indexgetO - static getReply(repliesList: ElementArrayFinder, replyNum: number) { - if (typeof (replyNum) === 'undefined') { - replyNum = 0; - } - - const reply = (replyNum === -1 ? repliesList.last() : repliesList.get(replyNum)); - return EditorPage.partsOfReply(reply); - } - - // Returns a Javascript object that can be used to access the parts (avatar, - // reply button, etc.) of a comment - // Usage example: - // expect(partsOfDcComment(commentDiv).regarding.inputSystem).toBe("th") - static partsOfComment(div: ElementFinder) { - const replies = div.all(by.repeater('reply in $ctrl.comment.replies')); // used in - // getReply() - // below - return { - wholeComment: div, - - // Left side controls - // avatar: - // div.element(by.binding('model.authorInfo.createdByUserRef.avatar_ref')), - avatar: div.element(by.css('.comment-footer img')), - author: div.element(by.binding('$ctrl.comment.authorInfo.createdByUserRef.name')), - date: div.element(by.binding('$ctrl.comment.authorInfo.createdDate | relativetime')), - score: div.element(by.css('.comment-interaction .likes')), - plusOneActive: div.element(by.css('.comment-actions .can-like')), - plusOneInactive: div.element(by.css('.comment-actions .liked')), - plusOne: div.element(by.css('.comment-actions i.fa-thumbs-o-up:not(.ng-hide)')), - - // Right side content - content: div.element(by.binding('$ctrl.comment.content')), - contextGuid: div.element(by.binding('$ctrl.comment.contextGuid')), - edit: { - textarea: div.element(by.model('$ctrl.editingCommentContent')), - updateBtn: div.element(by.buttonText('Update')), - cancelLink: div.element(by.linkText('Cancel')) - }, - regarding: { - // NOTE: Any or all of these may be absent in a given comment. Use - // isPresent() before calling expect(). - toggle: div.element(by.css('.comment-body > button')), - container: div.element(by.css('.commentRegarding')), - fieldValue: div.element(by.css('.regardingFieldValue')) - }, - - // Replies (below content but above bottom controls) - replies, - getReply(replyNum: number) { - return EditorPage.getReply(replies, replyNum); - }, - - // Bottom controls (below replies) - markOpenLink: div.element(by.css('.commentBottomBar i.fa-chevron-sign-up')), - markResolvedLink: div.element(by.css('.commentBottomBar i.fa-check')), - markTodoLink: div.element(by.css('.commentBottomBar i.fa-edit')), - editBtn: div.element(by.buttonText('Edit')), - replyBtn: div.element(by.buttonText('Reply')) - }; - } - - // Like partsOfComment, returns a Javascript object giving access to the parts - // of a reply - static partsOfReply(div: ElementFinder) { - return { - wholeReply: div, - content: div.element(by.model('reply.content')), - author: div.element(by.model('reply.authorInfo.createdByUserRef.name')), - date: div.element(by.model('reply.authorInfo.createdDate | relativetime')), - editLink: div.element(by.css('editReplyLink i.fa-chevron-sign-up')), - deleteLink: div.element(by.css('deleteReplyLink i.fa-trash')), - edit: { - input: div.element(by.css('form input')), - submit: div.element(by.css('form button[type="submit"]')), - cancel: div.element(by.css('form a i.fa-times')) - } - }; - } -} diff --git a/test/app/languageforge/lexicon/shared/editor.util.ts b/test/app/languageforge/lexicon/shared/editor.util.ts deleted file mode 100644 index ec27d77b76..0000000000 --- a/test/app/languageforge/lexicon/shared/editor.util.ts +++ /dev/null @@ -1,179 +0,0 @@ -import {browser, by, element, Key} from 'protractor'; -import {ElementArrayFinder, ElementFinder} from 'protractor/built/element'; - -// Utility functions for parsing dc-* directives (dc-multitext, etc) -export class EditorUtil { - // --- Parsing fields --- - - // Return the multitext's values as [{wsid: 'en', value: 'word'}, {wsid: 'de', value: 'Wort'}] - // NOTE: Returns a promise. Use .then() to access the actual data. - dcMultitextToArray = (elem: ElementFinder|ElementArrayFinder) => { - const inputSystemDivs = elem.all(by.repeater('tag in $ctrl.config.inputSystems')); - return inputSystemDivs.map((div: any) => { - const wsidSpan = div.element(by.css('.input-group > span.wsid')); - const wordInput = div.element(by.css('.input-group > .dc-text textarea')); - return wsidSpan.getText().then((wsid: any) => { - return wordInput.isPresent().then((isWordPresent: boolean) => { - if (isWordPresent) { - return wordInput.getAttribute('value').then((word: string) => { - return { - wsid, - value: word - }; - }); - } else { - return { wsid, value: '' }; - } - }); - }); - }); - } - - // Return the multitext's values as {en: 'word', de: 'Wort'} - // NOTE: Returns a promise. Use .then() to access the actual data. - dcMultitextToObject = (elem: ElementFinder|ElementArrayFinder) => { - return this.dcMultitextToArray(elem).then((values: any) => { - const result = {}; - for (let i = 0, l = values.length; i < l; i++) { - result[values[i].wsid] = values[i].value; - } - - return result; - }); - } - - // Returns the value of the multitext's first writing system, no matter what writing system is - // first. NOTE: Returns a promise. Use .then() to access the actual data. - dcMultitextToFirstValue = (elem: ElementFinder|ElementArrayFinder) => { - return this.dcMultitextToArray(elem).then((values: any) => { - return values[0].value; - }); - } - - static dcOptionListToValue(elem: any) { - const select = elem.element(by.css('.controls select')); - return select.element(by.css('option:checked')).getText().then((text: string) => { - return text; - }); - } - - // At the moment these are identical to dc-optionlist directives. - // When they change, this function will need to be rewritten - dcMultiOptionListToValue = EditorUtil.dcOptionListToValue; - - dcPicturesToObject = (elem: ElementFinder|ElementArrayFinder) => { - const pictures = elem.all(by.repeater('picture in $ctrl.pictures')); - return pictures.map((div: any) => { - const img = div.element(by.css('img')); - const caption = div.element(by.css('dc-multitext')); - return img.getAttribute('src').then((src: string) => { - return { - fileName: src.replace(/^.*[\\\/]/, ''), - caption: this.dcMultitextToObject(caption) - }; - }); - }); - } - - dcParsingFuncs = { - multitext: { - multitext_as_object: this.dcMultitextToObject, - multitext_as_array: this.dcMultitextToArray, - multitext_as_first_value: this.dcMultitextToFirstValue, - default_strategy: 'multitext_as_object' - }, - optionlist: EditorUtil.dcOptionListToValue, - multioptionlist: this.dcMultiOptionListToValue, - pictures: this.dcPicturesToObject - }; - - getParser(elem: any, multitextStrategy: string = this.dcParsingFuncs.multitext.default_strategy) { - const switchDiv = elem.element(by.css('[data-on="$ctrl.config.fields[fieldName].type"] > div')); - return switchDiv.getAttribute('data-ng-switch-when').then((fieldType: any) => { - let parser; - if (fieldType === 'multitext') { - parser = this.dcParsingFuncs[fieldType][multitextStrategy]; - } else { - parser = this.dcParsingFuncs[fieldType]; - } - - return parser; - }); - } - - parseDcField(elem: any, multitextStrategy?: string) { - return this.getParser(elem, multitextStrategy).then((parser: any) => parser(elem)); - } - - static getFields(searchLabel: string, rootElem: ElementFinder = element(by.className('dc-entry'))) { - return rootElem.all(by.cssContainingText('div[data-ng-repeat="fieldName in $ctrl.config.fieldOrder"]', searchLabel)).filter(elem => { - return elem.getText().then(text => { - // getText returns text including child elements, which for fields is generally on a new line - if(text.indexOf('\n') !== -1) text = text.substring(0, text.indexOf('\n')); - return text === searchLabel; - }) - }); - } - - getFieldValues(searchLabel: string, rootElem: ElementFinder = element(by.className('dc-entry')), - multitextStrategy?: string) { - return EditorUtil.getFields(searchLabel, rootElem).map( - (fieldElem: any) => this.parseDcField(fieldElem, multitextStrategy) - ); - } - - static getOneField(searchLabel: string, idx: number = 0, - rootElem: ElementFinder = element(by.className('dc-entry'))) { - return EditorUtil.getFields(searchLabel, rootElem).get(idx); - } - - getOneFieldValue(searchLabel: string, idx: number = 0, rootElem: ElementFinder = element(by.className('dc-entry')), - multitextStrategy?: string) { - const fieldElement = EditorUtil.getOneField(searchLabel, idx, rootElem); - return this.parseDcField(fieldElement, multitextStrategy); - } - - // For convenience in writing test code, since the values in testConstants don't match the - // displayed values. No need to worry about localization here, since E2E tests are all run in the - // English-language interface. - partOfSpeechNames = { - adj: 'Adjective', - adv: 'Adverb', - cla: 'Classifier', - n: 'Noun', - nprop: 'Proper Noun', - num: 'Numeral', - p: 'Particle', - prep: 'Preposition', - pro: 'Pronoun', - v: 'Verb' - }; - - // Take an abbreviation for a part of speech and return the value that will - // appear in the Part of Speech dropdown (for convenience in E2E tests). - expandPartOfSpeech(posAbbrev: string) { - return this.partOfSpeechNames[posAbbrev] + ' (' + posAbbrev + ')'; - } - - // designed for use with Text-Angular controls (i.e. that don't have ordinary input or textarea) - selectElement = { - async sendKeys(elem: any, keys: string) { - await elem.click(); - return elem.sendKeys(keys); - }, - - async clear(elem: any) { - // fix problem with protractor not scrolling to element before click - await elem.getLocation().then((navDivLocation: any) => { - const initTop = (navDivLocation.y - 150) > 0 ? navDivLocation.y - 150 : 1; - const initLeft = navDivLocation.x; - browser.executeScript('window.scrollTo(' + initLeft + ',' + initTop + ');'); - }); - - await elem.click(); - const ctrlA = Key.chord(Key.CONTROL, 'a'); - await elem.sendKeys(ctrlA); - return elem.sendKeys(Key.DELETE); - } - }; -} diff --git a/test/app/languageforge/lexicon/shared/lex-modals.util.ts b/test/app/languageforge/lexicon/shared/lex-modals.util.ts deleted file mode 100644 index 51caf04796..0000000000 --- a/test/app/languageforge/lexicon/shared/lex-modals.util.ts +++ /dev/null @@ -1,18 +0,0 @@ -import {by, element} from 'protractor'; - -export class LexModals { - modalBodyDiv = element(by.className('modal-body')); - modalFooterDiv = element(by.className('modal-footer')); - modalBodyText = element(by.id('modal-body-text')); - - // select language modal - selectLanguage = { - searchLanguageInput: this.modalBodyDiv.element(by.id('search-text-input')), - languageRows: this.modalBodyDiv.all(by.repeater('language in $ctrl.languages')), - firstLanguageName: this.modalBodyDiv - .all(by.repeater('language in $ctrl.languages').column('name')).first(), - lastLanguageName: this.modalBodyDiv.all(by.repeater('language in $ctrl.languages').column('name')).last(), - clearSearchButton: this.modalBodyDiv.element(by.className('clear-search-button')), - addButton: this.modalFooterDiv.element(by.id('select-language-add-btn')) - }; -} diff --git a/test/app/languageforge/lexicon/shared/new-lex-project.page.ts b/test/app/languageforge/lexicon/shared/new-lex-project.page.ts deleted file mode 100644 index 5b04df124b..0000000000 --- a/test/app/languageforge/lexicon/shared/new-lex-project.page.ts +++ /dev/null @@ -1,99 +0,0 @@ -import {browser, by, element} from 'protractor'; - -import {MockUploadElement} from '../../../bellows/shared/mock-upload.element'; -import {LexModals} from './lex-modals.util'; - -export class NewLexProjectPage { - private readonly mockUpload = new MockUploadElement(); - - modal = new LexModals(); - - static get() { - return browser.get(browser.baseUrl + '/app/lexicon/new-project'); - } - - // form controls - noticeList = element.all(by.repeater('notice in $ctrl.notices()')); - firstNoticeCloseButton = this.noticeList.first().element(by.partialButtonText('×')); - newLexProjectForm = element(by.id('new-lex-project-form')); - progressIndicatorStep3Label = element(by.id('progress-indicator-step3-label')); - backButton = element(by.id('back-button')); - nextButton = element(by.id('next-button')); - - async expectFormIsValid() { - expect(await this.nextButton.getAttribute('class')).toMatch(/btn-primary(?:\s|$)/); - } - - async expectFormIsNotValid() { - expect(await this.nextButton.getAttribute('class')).not.toMatch(/btn-primary(?:\s|$)/); - } - - formStatus = { - async expectHasNoError() { - expect(await element(by.id('form-status')).getAttribute('class')).not.toContain('alert-danger'); - }, - async expectContainsError(partialMsg: string) { - if (!partialMsg) partialMsg = ''; - expect(await element(by.id('form-status')).getAttribute('class')).toContain('alert-danger'); - expect(await element(by.id('form-status')).getText()).toContain(partialMsg); - } - }; - - // step 0: chooser - chooserPage = { - sendReceiveButton: element(by.id('send-receive-button')), - createButton: element(by.id('create-button')) - }; - // step 1: send receive credentials - srCredentialsPage = { - loginInput: element(by.id('sr-username')), - loginOk: element(by.id('username-ok')), - passwordInput: element(by.id('sr-password')), - credentialsInvalid: element(by.id('credentials-invalid')), - passwordOk: element(by.id('password-ok')), - projectNoAccess: element(by.id('project-no-access')), - projectOk: element(by.id('project-ok')), - projectSelect() { - return element(by.id('sr-project-select')); - } - }; - - // step 1: project name - namePage = { - projectNameInput: element(by.id('project-name')), - projectCodeInput: element(by.id('project-code')), - projectCodeUneditableInput: element(by.id('project-code-uneditable')), - projectCodeLoading: element(by.id('project-code-loading')), - projectCodeExists: element(by.id('project-code-exists')), - projectCodeAlphanumeric: element(by.id('project-code-alphanumeric')), - projectCodeOk: element(by.id('project-code-ok')), - editProjectCodeCheckbox: element(by.id('edit-project-code')) - }; - // step 2: initial data - initialDataPage = { - browseButton: element(by.id('browse-button')), - mockUpload: this.mockUpload - }; - // step 3: verify data - verifyDataPage = { - title: element(by.id('new-project-verify')), - nonCriticalErrorsButton: element(by.id('non-critical-errors-button')), - entriesImported: element(by.id('entries-imported')), - importErrors: element(by.id('import-errors')) - }; - // step 3 alternate: primary language - primaryLanguagePage = { - selectButton: element(by.id('select-language-button')), - // tslint:disable-next-line:max-line-length - // see http://stackoverflow.com/questions/25553057/making-protractor-wait-until-a-ui-boostrap-modal-box-has-disappeared-with-cucum - async selectButtonClick() { - await element(by.id('select-language-button')).click(); - return browser.executeScript('$(\'.modal\').removeClass(\'fade\');'); - } - }; - // step 3 alternate: send receive clone - srClonePage = { - cloning: element(by.id('cloning')) - }; - -} diff --git a/test/app/languageforge/lexicon/shared/project-settings.page.ts b/test/app/languageforge/lexicon/shared/project-settings.page.ts deleted file mode 100644 index 6cc129ceb4..0000000000 --- a/test/app/languageforge/lexicon/shared/project-settings.page.ts +++ /dev/null @@ -1,56 +0,0 @@ -import {browser, by, By, element, ExpectedConditions} from 'protractor'; - -import {ProjectsPage} from '../../../bellows/shared/projects.page'; - -export class ProjectSettingsPage { - private readonly projectsPage = new ProjectsPage(); - - conditionTimeout = 12000; - settingsMenuLink = element(by.id('settings-dropdown-button')); - projectSettingsLink = element(by.id('dropdown-project-settings')); - - // Get the projectSettings for project projectName - async get(projectName: string) { - await this.projectsPage.get(); - await this.projectsPage.clickOnProject(projectName); - return this.getByLink(); - } - - async getByLink() { - await browser.wait(ExpectedConditions.visibilityOf(this.settingsMenuLink), this.conditionTimeout); - await this.settingsMenuLink.click(); - await browser.wait(ExpectedConditions.visibilityOf(this.projectSettingsLink), this.conditionTimeout); - return this.projectSettingsLink.click(); - } - - tabDivs = element.all(by.className('tab-pane')); - activePane = element(by.css('div.tab-pane.active')); - - static getTabByName(tabName: string) { - return element(by.css('ul.nav-tabs')).element(by.cssContainingText('a', tabName)); - } - - tabs = { - project: ProjectSettingsPage.getTabByName('Project Properties') - }; - - projectTab = { - saveButton: this.tabDivs.get(0).element(by.id('project-settings-save-btn')), - defaultLanguageSelect: element(by.id('language')), - defaultLanguageSelected: element(by.css('#language option:checked')), - audioRecordingCodecSelect: element(by.id('codec')), - audioRecordingCodecSelected: element(by.id('#codec option:checked')), - whenToConvertAudioSelect: element(by.id('conversion')), - whenToConvertAudioSelected: element(by.id('#conversion option:checked')), - }; - - /** Second parameter is optional, default false. If true, fieldName will be considered - * a regular expression that should not be touched. If false or unspecified, fieldName - * will be considered an exact match (so "Etymology" should not match "Etymology Comment"). - */ - static getFieldByName(fieldName: string, treatAsRegex: boolean) { - const fieldRegex: string|RegExp = (treatAsRegex ? fieldName : new RegExp('^' + fieldName + '$')); - return element(By.css('div.tab-pane.active dl.picklists')) - .element(By.cssContainingText('div[data-ng-repeat]', fieldRegex)); - } -} diff --git a/test/app/protractorConf.js b/test/app/protractorConf.js deleted file mode 100644 index b929725198..0000000000 --- a/test/app/protractorConf.js +++ /dev/null @@ -1,66 +0,0 @@ -var failFast = require('protractor-fail-fast'); -var specString = '*'; -var specs = [ - "/data/test/app/allspecs/**/*.e2e-spec.js", - "/data/test/app/bellows/**/" + specString + ".e2e-spec.js", - "/data/test/app/languageforge/**/" + specString + ".e2e-spec.js", -]; -exports.config = { - seleniumAddress: 'http://selenium:4444/wd/hub', - baseUrl: 'http://app-for-e2e', - allScriptsTimeout: 12000, - capabilities: { - browserName: 'chrome', - chromeOptions: { - args: ['--start-maximized'] - } - }, - specs: specs, - framework: 'jasmine2', - rootElement: '[id="app-container"]', - jasmineNodeOpts: { - showColors: true, - defaultTimeoutInterval: 120000, - print: function () {} - }, - onPrepare: function () { - /* global angular: false, browser: false, jasmine: false */ - - browser.driver.manage().window().maximize(); - - var jasmineReporters = require('jasmine-reporters'); - if (process.env.GITHUB_ACTIONS) { - // https://github.com/angular/protractor-cookbook/tree/master/jasmine-junit-reports - var junitReporter = new jasmineReporters.JUnitXmlReporter({ - savePath: 'e2e-output/', - consolidateAll: true - - // results written to file: e2e-output/junitresults.xml - - }); - jasmine.getEnv().addReporter(junitReporter); - } - var SpecReporter = require('jasmine-spec-reporter').SpecReporter; - jasmine.getEnv().addReporter(new SpecReporter({ - spec: { - displayStacktrace: true - } - })); - - // Uncomment to pause tests on first failure - /* - var pauseOnFailure = { - specDone: function (spec) { - if (spec.status === 'failed') { - debugger; - } - } - }; - jasmine.getEnv().addReporter(pauseOnFailure); - */ - }, - plugins: [failFast.init()], - afterLaunch: function () { - failFast.clean(); // Removes the fail file once all test runners have completed. - } -}; diff --git a/test/app/setupTestEnvironment.php b/test/app/setupTestEnvironment.php deleted file mode 100644 index de5f5f0463..0000000000 --- a/test/app/setupTestEnvironment.php +++ /dev/null @@ -1,319 +0,0 @@ - 1) { - // hostname is passed in on command line - $hostname = $argv[1]; -} -$_SERVER["HTTP_HOST"] = $hostname; - -// start with a fresh database -MongoStore::dropAllCollections(DATABASE); - -// Also empty out databases for the test projects -$projectArrays = [ - $constants["testProjectName"] => $constants["testProjectCode"], - $constants["otherProjectName"] => $constants["otherProjectCode"], - $constants["thirdProjectName"] => $constants["thirdProjectCode"], - $constants["fourthProjectName"] => $constants["fourthProjectCode"], - $constants["newProjectName"] => $constants["newProjectCode"], - $constants["emptyProjectName"] => $constants["emptyProjectCode"], - $constants["srProjectName"] => $constants["srProjectCode"], -]; - -foreach ($projectArrays as $projectName => $projectCode) { - $projectModel = new ProjectModel(); - $projectModel->projectName = $projectName; - $projectModel->projectCode = $projectCode; - MongoStore::dropAllCollections($projectModel->databaseName()); - MongoStore::dropDB($projectModel->databaseName()); -} - -$adminUserId = UserCommands::createUser([ - "name" => $constants["adminName"], - "email" => $constants["adminEmail"], - "password" => $constants["adminPassword"], -]); -$adminUserId = UserCommands::updateUser([ - "id" => $adminUserId, - "name" => $constants["adminName"], - "email" => $constants["adminEmail"], - "username" => $constants["adminUsername"], - "password" => $constants["adminPassword"], - "active" => true, - "role" => SystemRoles::SYSTEM_ADMIN, -]); -$managerUserId = UserCommands::createUser([ - "name" => $constants["managerName"], - "email" => $constants["managerEmail"], - "password" => $constants["managerPassword"], -]); -$managerUserId = UserCommands::updateUser([ - "id" => $managerUserId, - "name" => $constants["managerName"], - "email" => $constants["managerEmail"], - "username" => $constants["managerUsername"], - "password" => $constants["managerPassword"], - "active" => true, - "role" => SystemRoles::USER, -]); -$memberUserId = UserCommands::createUser([ - "name" => $constants["memberName"], - "email" => $constants["memberEmail"], - "password" => $constants["memberPassword"], -]); -$memberUserId = UserCommands::updateUser([ - "id" => $memberUserId, - "name" => $constants["memberName"], - "email" => $constants["memberEmail"], - "username" => $constants["memberUsername"], - "password" => $constants["memberPassword"], - "active" => true, - "role" => SystemRoles::USER, -]); -$member2UserId = UserCommands::createUser([ - "name" => $constants["member2Name"], - "email" => $constants["member2Email"], - "password" => $constants["member2Password"], -]); -$member2UserId = UserCommands::updateUser([ - "id" => $member2UserId, - "name" => $constants["member2Name"], - "email" => $constants["member2Email"], - "username" => $constants["member2Username"], - "password" => $constants["member2Password"], - "active" => true, - "role" => SystemRoles::USER, -]); -$expiredUserId = UserCommands::createUser( - [ - "name" => $constants["expiredName"], - "email" => $constants["expiredEmail"], - "password" => $constants["memberPassword"], - ] // intentionally set wrong password -); -$expiredUserId = UserCommands::updateUser([ - "id" => $expiredUserId, - "name" => $constants["expiredName"], - "email" => $constants["expiredEmail"], - "username" => $constants["expiredUsername"], - "password" => $constants["memberPassword"], // intentionally set wrong password - "active" => true, - "role" => SystemRoles::USER, -]); -$resetUserId = UserCommands::createUser( - [ - "name" => $constants["resetName"], - "email" => $constants["resetEmail"], - "password" => $constants["memberPassword"], - ] // intentionally set wrong password -); -$resetUserId = UserCommands::updateUser([ - "id" => $resetUserId, - "name" => $constants["resetName"], - "email" => $constants["resetEmail"], - "username" => $constants["resetUsername"], - "password" => $constants["memberPassword"], // intentionally set wrong password - "active" => true, - "role" => SystemRoles::USER, -]); -$observerUserId = UserCommands::createUser([ - "name" => $constants["observerName"], - "email" => $constants["observerEmail"], - "password" => $constants["observerPassword"], -]); -$observerUserId = UserCommands::updateUser([ - "id" => $observerUserId, - "name" => $constants["observerName"], - "email" => $constants["observerEmail"], - "username" => $constants["observerUsername"], - "password" => $constants["observerPassword"], - "active" => true, - "role" => SystemRoles::USER, -]); - -// set forgot password with expired date -$today = new DateTime(); -$expiredUser = new UserModel($expiredUserId); -$expiredUser->resetPasswordKey = $constants["expiredPasswordKey"]; -$expiredUser->resetPasswordExpirationDate = $today; -$expiredUser->write(); - -// set forgot password with valid date -$resetUser = new UserModel($resetUserId); -$resetUser->resetPasswordKey = $constants["resetPasswordKey"]; -$resetUser->resetPasswordExpirationDate = $today->add(new DateInterval("P5D")); -$resetUser->write(); - -$projectType = null; -$projectType = LfProjectModel::LEXICON_APP; -$testProjectId = ProjectCommands::createProject( - $constants["testProjectName"], - $constants["testProjectCode"], - $projectType, - $adminUserId -); -$testProjectModel = new ProjectModel($testProjectId); -$testProjectModel->projectCode = $constants["testProjectCode"]; -$testProjectModel->allowInviteAFriend = $constants["testProjectAllowInvites"]; -$testProjectModel->write(); - -$otherProjectId = ProjectCommands::createProject( - $constants["otherProjectName"], - $constants["otherProjectCode"], - $projectType, - $managerUserId -); -$otherProjectModel = new ProjectModel($otherProjectId); -$otherProjectModel->projectCode = $constants["otherProjectCode"]; -$otherProjectModel->allowInviteAFriend = $constants["otherProjectAllowInvites"]; -$otherProjectModel->write(); - -$fourthProjectId = ProjectCommands::createProject( - $constants["fourthProjectName"], - $constants["fourthProjectCode"], - $projectType, - $managerUserId -); -$fourthProjectModel = new ProjectModel($fourthProjectId); -$fourthProjectModel->projectCode = $constants["fourthProjectCode"]; -$fourthProjectModel->allowInviteAFriend = $constants["fourthProjectAllowInvites"]; -$fourthProjectModel->write(); - -$srProject = [ - "identifier" => $constants["srIdentifier"], - "name" => $constants["srName"], - "repository" => "https://public.languagedepot.org", - "role" => "manager", -]; -$srTestProjectId = ProjectCommands::createProject( - $constants["srProjectName"], - $constants["srProjectCode"], - $projectType, - $managerUserId, - $srProject -); - -ProjectCommands::updateUserRole($testProjectId, $managerUserId, ProjectRoles::MANAGER); -ProjectCommands::updateUserRole($testProjectId, $memberUserId, ProjectRoles::CONTRIBUTOR); -ProjectCommands::updateUserRole($testProjectId, $member2UserId, ProjectRoles::CONTRIBUTOR); -ProjectCommands::updateUserRole($testProjectId, $resetUserId, ProjectRoles::CONTRIBUTOR); -ProjectCommands::updateUserRole($otherProjectId, $adminUserId, ProjectRoles::MANAGER); -ProjectCommands::updateUserRole($fourthProjectId, $adminUserId, ProjectRoles::MANAGER); -ProjectCommands::updateUserRole($srTestProjectId, $adminUserId, ProjectRoles::MANAGER); - -// Set up LanguageForge E2E test envrionment here -ProjectCommands::updateUserRole($testProjectId, $observerUserId, LexRoles::OBSERVER); -$testProjectModel = new LexProjectModel($testProjectId); -$testProjectModel->addInputSystem("th-fonipa", "tipa", "Thai"); -$testProjectModel->config->entry->fields[LexConfig::LEXEME]->inputSystems[] = "th-fonipa"; -$testProjectModel->addInputSystem("th-Zxxx-x-audio", "taud", "Thai Voice"); -$testProjectModel->config->entry->fields[LexConfig::LEXEME]->inputSystems[] = "th-Zxxx-x-audio"; -$testProjectId = $testProjectModel->write(); - -// setup to mimic file upload -$fileName = $constants["testEntry1"]["lexeme"]["th-Zxxx-x-audio"]["value"]; -$file = []; -$file["name"] = $fileName; -$_FILES["file"] = $file; - -// put a copy of the test file in tmp -$tmpFilePath = sys_get_temp_dir() . "/CopyOf$fileName"; -copy(TestPath . "common/$fileName", $tmpFilePath); - -$response = LexUploadCommands::uploadAudioFile($testProjectId, "audio", $tmpFilePath); - -// cleanup tmp file if it still exists -if (file_exists($tmpFilePath) && !is_dir($tmpFilePath)) { - @unlink($tmpFilePath); -} - -// put uploaded file into entry1 -$constants["testEntry1"]["lexeme"]["th-Zxxx-x-audio"]["value"] = $response->data->fileName; - -// setup to mimic file upload -$fileName = $constants["testEntry1"]["senses"][0]["pictures"][0]["fileName"]; -$file = []; -$file["name"] = $fileName; -$_FILES["file"] = $file; - -// put a copy of the test file in tmp -$tmpFilePath = sys_get_temp_dir() . "/CopyOf$fileName"; -copy(TestPath . "common/$fileName", $tmpFilePath); - -$response = LexUploadCommands::uploadImageFile($testProjectId, "sense-image", $tmpFilePath); - -// cleanup tmp file if it still exists -if (file_exists($tmpFilePath) && !is_dir($tmpFilePath)) { - @unlink($tmpFilePath); -} - -// put uploaded file into entry1 -$constants["testEntry1"]["senses"][0]["pictures"][0]["fileName"] = $response->data->fileName; - -$entry1 = LexEntryCommands::updateEntry( - $testProjectId, - [ - "id" => "", - "lexeme" => $constants["testEntry1"]["lexeme"], - "senses" => $constants["testEntry1"]["senses"], - ], - $managerUserId -); -$entry2 = LexEntryCommands::updateEntry( - $testProjectId, - [ - "id" => "", - "lexeme" => $constants["testEntry2"]["lexeme"], - "senses" => $constants["testEntry2"]["senses"], - ], - $managerUserId -); -$multipleMeaningEntry1 = LexEntryCommands::updateEntry( - $testProjectId, - [ - "id" => "", - "lexeme" => $constants["testMultipleMeaningEntry1"]["lexeme"], - "senses" => $constants["testMultipleMeaningEntry1"]["senses"], - ], - $managerUserId -); - -// put mock uploaded zip import (jpg file) -$fileName = $constants["testMockJpgImportFile"]["name"]; -$tmpFilePath = sys_get_temp_dir() . DIRECTORY_SEPARATOR . $fileName; -copy(TestPath . "common/$fileName", $tmpFilePath); - -// put mock uploaded zip import (zip file) -$fileName = $constants["testMockZipImportFile"]["name"]; -$tmpFilePath = sys_get_temp_dir() . DIRECTORY_SEPARATOR . $fileName; -copy(TestPath . "common/$fileName", $tmpFilePath); - -// put mock uploaded audio (png file) -$fileName = $constants["testMockPngUploadFile"]["name"]; -$tmpFilePath = sys_get_temp_dir() . DIRECTORY_SEPARATOR . $fileName; -copy(TestPath . "common/$fileName", $tmpFilePath); - -// put mock uploaded audio (mp3 file) -$fileName = $constants["testMockMp3UploadFile"]["name"]; -$tmpFilePath = sys_get_temp_dir() . DIRECTORY_SEPARATOR . $fileName; -copy(TestPath . "common/$fileName", $tmpFilePath); diff --git a/test/app/testConstants.json b/test/app/testConstants.json deleted file mode 100644 index a9c4c7ba29..0000000000 --- a/test/app/testConstants.json +++ /dev/null @@ -1,193 +0,0 @@ -{ - "Bellows_Test_Constants": "", - - "siteType": "languageforge", - "siteHostname": "e2e", - "baseUrl": "http://e2e", - "conditionTimeout": 8000, - - "adminUsername": "test_runner_admin", - "adminName": "Test Admin", - "adminPassword": "hammertime", - "adminEmail": "test_runner_admin@example.com", - "managerUsername": "test_runner_manager_user", - "managerName": "Test Manager", - "managerPassword": "manageruser1", - "managerEmail": "test_runner_manager_user@example.com", - "memberUsername": "test_runner_normal_user", - "memberName": "Test User", - "memberPassword": "normaluser1", - "memberEmail": "test_runner_normal_user@example.com", - "member2Username": "test_runner_normal_user_2", - "member2Name": "Test Second User", - "member2Password": "normaluser2", - "member2Email": "test_runner_normal_user_2@example.com", - "expiredUsername": "test_runner_expired_normal_user", - "expiredName": "Test ExpiredUser", - "expiredPassword": "normaluser3", - "expiredEmail": "test_runner_expired_normal_user@example.com", - "expiredPasswordKey": "fa6c056d5155059c4f067e77c4cb420fcfc3762f", - "resetUsername": "test_runner_reset_normal_user", - "resetName": "Test ResetUser", - "resetPassword": "normaluser4", - "resetEmail": "test_runner_reset_normal_user@example.com", - "resetPasswordKey": "122240670b5136bdb260d02feaff098280c33bd4", - "observerUsername": "test_runner_observer_normal_user", - "observerName": "Test ObserverUser", - "observerPassword": "normaluser5", - "observerEmail": "test_runner_observer_normal_user@example.com", - "writableUsername": "test_runner_writable_user", - "writableName": "Test WritableUser", - "writablePassword": "writableuser5", - "writableEmail": "test_runner_writable_user@example.com", - "unusedUsername": "test_runner_unused_user", - "unusedName": "Test UnusedUser", - "unusedEmail": "test_runner_unused_user+test@example.com", - "passwordTooShort": "123456", - "passwordValid": "languageforge", - "emailNoAt": "email", - "emailValid": "email@example.com", - - "avatar": "/Site/views/shared/image/avatar/anonymoose.png", - - "testProjectName": "Test Project", - "testProjectCode": "test_project", - "testProjectAllowInvites": false, - "otherProjectName": "Other Project for Testing", - "otherProjectCode": "otherproject", - "otherProjectAllowInvites": true, - "thirdProjectName": "new kid on the block", - "thirdProjectCode": "nkotb", - "fourthProjectName": "Fourth Project for Testing", - "fourthProjectCode": "fourthproject", - "fourthProjectAllowInvites": true, - "newProjectName": "New Project", - "newProjectCode": "new_project", - "emptyProjectName": "Empty Project", - "emptyProjectCode": "empty_project", - "srProjectName": "SR Project", - "srProjectCode": "sr_project", - "srIdentifier": "mock-id1", - "srName": "mock-name1", - "srUsername": "sr-mock-username", - "srPassword": "sr-mock-password", - - "LanguageForge_Test_Constants": "", - - "Language added to new project": "", - "searchLanguage": "Spanish", - "foundLanguage": "español", - - "Entries added during setup": "", - "testEntry1": { - "lexeme": { - "th": { "value": "ข้าวผัดหมู" }, - "th-fonipa": { "value": "khâaw phàt mǔu" }, - "th-Zxxx-x-audio": { "value": "TestAudio.mp3" } - }, - "senses": [ - { - "definition": { "en": { "value": "fried rice with pork" } }, - "pictures": [{ "fileName": "FriedRiceWithPork.jpg", "caption": { "en": { "value": "fried rice with pork" } } }], - "partOfSpeech": { "value": "n" }, - "semanticDomain": { "values": ["1.1"] } - } - ] - }, - "testEntry2": { - "lexeme": { "th": { "value": "หน่อไม้ฝรั่งผัดกุ้ง" }, "th-fonipa": { "value": "nɔ̀máay fàràŋ phàt kûŋ" } }, - "senses": [ - { "definition": { "en": { "value": "asparagus with shrimp over rice" } }, "partOfSpeech": { "value": "n" } } - ] - }, - - "Entries added by various tests": "", - "testEntry3": { - "lexeme": { "th": { "value": "ผัดชีอิ้วหมู" }, "th-fonipa": { "value": "phàt siiʔ ǐw mǔu" } }, - "senses": [ - { "definition": { "en": { "value": "noodles fried in soy sauce with pork" } }, "partOfSpeech": { "value": "n" } } - ] - }, - "testEntry4": { - "lexeme": { "th": { "value": "กระเพาหมู" }, "th-fonipa": { "value": "krapâw mǔu" } }, - "senses": [ - { "definition": { "en": { "value": "stir fried basil and pork over rice" } }, "partOfSpeech": { "value": "n" } } - ] - }, - "testEntry5": { - "lexeme": { "th": { "value": "ไก่สกกระเกียม" }, "th-fonipa": { "value": "kài sòt kràthiam" } }, - "senses": [ - { "definition": { "en": { "value": "stir fried garlic chicken over rice" } }, "partOfSpeech": { "value": "n" } } - ] - }, - - "Entries with multiple meanings and optional fields": "", - "testMultipleMeaningEntry1": { - "lexeme": { "th": { "value": "ว่า" }, "th-fonipa": { "value": "wâa" } }, - "senses": [ - { - "definition": { "en": { "value": "that, as" } }, - "partOfSpeech": { "value": "prep" }, - "generalNote": { "en": { "value": "Most common usage" } }, - "examples": [ - { - "sentence": { "th": { "value": "ผมยังอดสงสัยไม่ได้ว่า" } }, - "translation": { "en": { "value": "I can't help but think that..." } } - }, - { - "sentence": { "th": { "value": "เชื่อกันมานมนานแล้วว่า" } }, - "translation": { "en": { "value": "We have believed for a long time that..." } } - } - ], - "source": { "en": { "value": "http://www.thai-language.com/id/131403" } } - }, - { - "definition": { "en": { "value": "say, speak" } }, - "partOfSpeech": { "value": "v" }, - "generalNote": { "en": { "value": "This meaning is almost as common" } }, - "examples": [ - { - "sentence": { "th": { "value": "คำนี้ภาษาอังกฤษว่ายังไง" } }, - "translation": { "en": { "value": "How do you say this word in English?" } } - }, - { - "sentence": { "th": { "value": "ว่าแต่เขา อิเหนาเป็นเอง" } }, - "translation": { "en": { "value": "The pot calls the kettle black." } } - } - ], - "source": { "en": { "value": "http://www.thai-language.com/id/131403#def1c" } } - } - ] - }, - - "testMockZipImportFile": { - "lastModified": 1416986856000, - "name": "TestLexProject.zip", - "size": 28999, - "type": "", - "webkitRelativePath": "" - }, - "testMockJpgImportFile": { - "lastModified": 1416986856000, - "name": "TestImage.jpg", - "size": 2753, - "type": "", - "webkitRelativePath": "" - }, - "testMockPngUploadFile": { - "lastModified": 1416986856000, - "name": "TestImage.png", - "size": 5652, - "type": "", - "webkitRelativePath": "" - }, - "testMockMp3UploadFile": { - "lastModified": 1416986856000, - "name": "TestAudio.mp3", - "size": 21064, - "type": "", - "webkitRelativePath": "" - }, - - "End_of_constants": "" -} diff --git a/test/app/tsconfig.json b/test/app/tsconfig.json deleted file mode 100644 index 6a6526d1b9..0000000000 --- a/test/app/tsconfig.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "buildOnSave": false, - "compileOnSave": false, - "compilerOptions": { - "alwaysStrict": true, - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "lib": ["es7", "dom"], - "module": "commonjs", - "moduleResolution": "Node", - "noImplicitAny": true, - "rootDir": ".", - "sourceMap": true, - "suppressImplicitAnyIndexErrors": true, - "target": "ES6", - "typeRoots": ["node_modules/@types"] - }, - "exclude": ["node_modules"], - "include": ["**/*.ts"] -} diff --git a/test/common/TestLexProject/audio/TestAudio.mp3 b/test/common/TestLexProject/audio/TestAudio.mp3 deleted file mode 100644 index bb23b552efa67f1e274b2942e751e3d0a094986b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20616 zcmdqI_ghmz*Y~><0)$>gx&cD(O{o%k@4fflyC6vDU3xD9(wiV%Q9wEf(yK@jL7IZ1 zC#6#Ea0|97^oTuVcrQamu#Set2e^H z(LcaIM^O;-0V(EE^|M8&AymA5++Dr=y}jhMH8nAxGGeaVrtj+I?BR&;_wjaj^g;xC z`#B((126`d|3AMoF){hye+OQr!~SdPfB&oM<>Za|mIQMN0Dv|KU}NLslak)KLraT5 zu(EP-2?&UaN=Tqkii(PAYC1ZGhGu3qHja*NZazMNfuW&jbZl&FQc`+)PEKKASy@$8 zeSJ$yN5`vI{r$tk!xIxTGmDFBYg=1iz8oH&p8ooEc?p7;zQi?TH5G;V#Krv}_5b(g zDA7o)ekA}v-`KKwS|B~0rW$X_$?G_kEHL9%r)@$Um4=~9?2F;z8BQ}Fo7fX5R8)n z|Hw>;!PMjsQ8dB9K`{yn3aFN_d$@yJ0E!d_5eT~%MjB*A5)DG0{VOB9$43HEL~ozU zgOhzx02<+EaW5o7v(x%T+s5y!$6-;L2oIbkJVp)Zo^zvWx+}A9Apd z+_L&ZrvZpj?8eM31qSME7Co)e*&aFh-j8vt$jF!9+`D=L4~YtwnB5M0KHJNzpTtLi zZ^1Cl)4c^|{L~IsEK-`&CjzGlhMVtA?mgwct1jDQnFo5Tg@YHS$kdh@l17|Fp`2xb zJ?q7M-(Jrx=c@fjWxfh<2O&W%lEKeIgAG*Jk?-)zW=#=)0;m~MsYsh?w$_BceG|BJ z!^zy#Ke#vd@8rStRiE&?E1?ABl24$+4a&Z^&z^hCc0RX*X)bg_Fhgr#we|S#~5YmSz_rw8YPe_~|2=oJj zN=w{UunYxzUFl&58>abW4o0HPo_c{unhK;=p4{3!l>q+n&yn`sdA3b`0kKX@U zVQ?#b^F|a2W!VJ(D&VF)1@PXDJ$-uIGI4x74Ib4};=`c4ZU%TJ#1rpUlKO+YT{sxW}e%U^nl!+ZMg2QoVm(1th7I9J6IclBTOL zl0@ugcSOm6>Nx0RQB+ltyJMSr#PU&P1&SJu7dm9xBtRIT9KUn=^Ma~6YWFO$u0H0& zG1_V|(z?xLE8vO%-^3O1w5_f3886+Q)%DV}TKlEMCo6cALU=&7j|!5692{59AruCW zX`wtkNIYa^rpjl(bz~;6Vk1CG`J5PcTQa6#q1i^aR_jiOU2CvU?Ovy#cKpop=A?j+ zSu9?NZqd`&m$JhLX5T==w7!!KAxrF(gnk?42pC~{)6+j;-4;;rUnR<+Oa{DJrVJG2 z?pT`)22Xy?m>>vo?$zy?YBfA@5~Z!Xn>+0Mg)HHjyxNG0akj zv3#45hHj1o>BlM6o4zZ|ItKv1|*v04(RPoHotF8^m2ZB-242o?*~ggPEN5wPC>zGzt$4z(FkfDDH1+><*#<;;cM0 z8rERROY^|ojm9qb=GX2X=_qP|WULFPpbV8FOfX^XoALbWJFSmR_5G|4z_xg@eH<9#rLG2u5a#$>0Nas;r=NTOP?GI$H|002i0~aQ^%1R zJ;+Q|Br(n)B=Q$^*=!rpr$+wvUlm?b8_r6Fz4WqKG_q(^cPjv++I6R&$a%Ow-JJ`hNUqUS1N(p5XsogT40 zL*Ni$Px55ORk2Z?a|)s$XUj4g1yg0=i{-%Ox*9%L@&gsDtE7A0!?dw^SW!4|tnVgF z%HRv|2le%)H3)*&*@*MCL9X1K43V#5q{GTDttnDJCJygxi9>3W$WiC+YB2;hkq6g+B6?NVc-!W|e*)dehJbJ#v30J^i_) z$hzv}T3Bq_+_#?NAw()ekb{fkcP*0f4w-`7o}cOy^#%3A@Vcb}n|U{7Wa`+ipE66v zSTu?xt{6%UPgpqnD;#=*YMDAf-uO)VWIFD=lUE<{ELCcsX-uqbJ~7fKbn+EE8r7*cq%_$D;QI_Xdd?sG zw)}E6@Ts}y+D2!WtVu1DAbl@wiGh))t^ExCMdVdueZ0jwei~|3;tuVaFVSzoRna9| zx~#nJ$1xv<4~#ea2ItAK$BQ*Sf7C&rZG^%L33}(l-mWIkgaw1M3@+b6@Z6j?)6_F} z-IEHl)~+B{Rvy_u=k{~FPVDOv*!B9<oqfdB1zVTt6WAdz}yihfA5QVtgCF zO8S5VEnf&BekstomqZ$v{!hgxJkkA4*E1Ml-eS<^fjw_o=<%`2Ts8u2yK;4a_q^*!@W@j7lG^msNuTiL z)duzCMdRdbuys{VN7J0W)YVLM9(Y6v{vmd>*vouV*?AI_J4zaZcq}Vp5lcmPcPVFz zNBvy4=avu&fW=Ou<0aJ(I3V$-FocrRJr%pp&=6;GmtaN#pusen+2z#keH1&J2pO+J zySeM}w_d--69_ezln)m$|*Gs7niNSGag!{<*RzWtVzKMy;SXsZ@w4FE3-~Z zec(rK$5)nKuQ$9lJ(W895!gi8-Ga9JZc%Zemda-8F;-86`J^Vu&h&N?NE2dh$?;sX z$MrTY_FQQSPn(!UcVoqhnANM?2hsH!j`(3ZCF_9m;GgZ z;W0}2vs+h5ElgGY$^;w=p)Lg|Su{Fgcmd&V!ICGEr!{00Y;Yxh9pLZITeu#yw&9sk z31`D+lBA+nVzWMeNt`sse2}C zx*0|hN5!loGRcTp=_#b|_!>2QSP#DkemTVc`)UaU*K5=L)K7VTE8#r;niqZ(G%vI zHSk}H3}%+Yv@^)`BZkEONA;v`KX$X`vCerVHt0|YB$9!2h#JRJwX$b zaz}!Q^II@pN0BIe0RYp_8s| z@sJNMD!$*@qIf)G3x(|*ffb1lnGgnLg(@oE?Fi|K(3;XVY!7+-2f(a>KMN*4|B+(N`q{(!VgA;e1ZrwFkr75xFckAb^8_Egz z6m=0dQX20F>BYjaX~44kkULc52}3!&=AJ>T9xpL7fH|+7Xg1u~TDMeza9K5X6n0C9 z2#tj;vFIgG2D93iU>KnU2u=2VyMLp};^>bq7-s-o5=KA_9>>v2k5(Y%O~)olPG+5% zRTGxP7gSe%^HHy7FQBV~m_w5;hv8yxcw=rSfzu>Am0{pl`~Z;zxOK_L_v_}43-h{1 zt~hTe<|dZ8NZ~02X7PrMK5y}aR?g%yzQKIR2z4C-DE;8I)%)Mr0USV1!m z9!zHx9Rj3krVGo1m@d5H!8dLK!7|qmmss7e9MF_r!2K1rFs1P5FAF@j)upNbvknyr ztQxc0zzm0vh;|zRwUjCAnlfD)?L4>hp~SC@o#M06?*lgEJ1p;cK4vV}6^VnDNw_?| z2x^z$zrnbU_Dxo<(%QF!k(q^3a_R-XNoT#tN4k7ga}Si$J(;B4t4M(&4=n#Xz5j2& z7@PZ2qnZnnMl2^UP?s>$15(r@T(;PaZy$Jxlfka}<}U4Yo9;L4owIjg5l0QSW0q!J%e!O?QYLQ@ z=<);bK}zD|W&*XLTyRdxe4hHG1;dewFI>qy>I{EFb`_Ix2iaLUy}5hTpq>|I zGD6pct|Sgr8AydtSXewAe%!GwmE{jLHRP@f)UXq6spGOKTAYpD9!WRy0U6Ep;O_5A z_hvwirC3imyBeQ5ZurhT%Yy^?rT1LjL!Y&RoWXs=y1!tj)h!_e8d|}-=!uCGH0)o6 z*5m_E5m}1jHE2kGHpYiep%wV`oconV%?uKP=sJOxoQnYGBJ*9~E)q?F;Lq6dqXcRV z7;)Er*g;;Eps$>^vgahY4t3zvDApFbK}BQm;q>s64adr@*&E~i6hbLz$oB;XNgFPW zE9;(5^q{s5+$9BoT!7nD4C zVt&5c4du-+ke|}?1K+yQk8pjwL(4R&@j9N0%v+navq4U)a4|VT=K3gNz*FZF@bHjV zWs63hOn{~G_#xK&h+l@i-H*jDONlwU?~Qm3I?#MRl_bGf%y{ zEZdybQVR}bsPsb5sxvZA@Yk*7W6?}VX@Ut=x=}KN3SPfvN;+YIiR`4^}zh%=4*HfWWG@w-|(F|FM zz)tqeX&=IoPSlI8LBzIj&=if!aqK_H&!J#YJT4`5FuNthh{nohHT-6~1^89gH@%kv z2q|;Sd*acMCz%bw0;hm)E>b#KR@}i5nf8b3Jtlr^&ae3P&8V~CjlT6SO^tr$2cdG- z&UP7ujNs0!an&Yx=FmOU4`6vV0a20W9`~sUaqw3HcVpWPZQCafKBcI^iah;W6dE5> zRxzvFaIR1iPVJIWS})8Lvq_{0{n5%kR3wNI8%UQmC7YTEH5c48{v}i}8tm86VR`uM zrmij@ru5xK)#Lb=9Q*Wp(QV#SX*9 z%1r50oKno5@N30?aDIP=0AU|^3ZPJ5AjT?jvP?#m#Q!tPT_YE?9Jc@)Sdr#vFOW|DbnVQHeKqaK+O z*X(w;#$`5^CcD_?-M>RX9%54L^(a}cxIkwJ(e^Gdey>exS!tljtW3B?$|06cG}^Q+ z^xyI!U*C<-Of#9)FZp^=X3ro>-<9mT)Jz9iY8EA9L_>C|vcd}k&LN%*KgnyG_w~Q^ zIvGEjzK_Dw@&hYPgi&_S1s~^q40%f~Wk%1bL#ayndS#dyL*Xh zJr2L&_1{mxFB%?M%vkgFLPRc?@T+8s`=+#GY=GE z-JwY?e!H96lCd+ht%j~psrnSxBzN5WlmfcY-7?4FG(!4M&2W;vMZs}%9So}nkRMS{ z%PwF|B+Wj*m7;}8kNr^k$#Jb(hyR^{uQR_KZwZm2p=IJr-=zVVVZ6l2ei@L|%kSPk zfEF|&Jm_o@0FN5kCL7iuC7Qm#eOaU)rUG zh3;Z297vLHNQ;}uAnq)zLuhnbw_X~hD{9|GX`rr@8fF4z5kEu_=^>}&GD-_+msEv= zh?bW0i9}j_#G~4@N{@*mm6x~#KKHq!|V8snNM9e_ooHb8Lj%KVYxJi4Dw9fhDHrI+y}Z zg*b}3EUUEB+ifDw$`1}*UzmmFJk9)Vh}7E2{p!2>`tBTgQWI0D$Q0+hto4V))DK(* znj5VSosItv8gx8ya^fr~@ac2@27cOS+PsXoCBzCqlaVtxj%dIjrcf2LAHrA3Q^Mrs zAnN2_ml^v3%#JVulMU!MpPtyN-Ylh7_~x{A`Uu-o`m)eueny`?Mb}X;lwPQp)Wur8 zRzV&)!(*7B;8-{t%YI4Xa@Wzxe36^`0k{EPN9_ju_7eYN{tKioo5y0m0F{T z#h%GyohA0(plG5aN7)V$Sg!TPU}FTXocaUH3y@5H1ZStV47`RtWA zt=Lg^{=i%kXA9gCn_S!}g#rg#yB$C{ZOR~nR_RozJ^}%98st&G1Gp+kyN>r|W3Vck zD~?A6@eLnr3b>g*=wr{@+93Q%pUP$$8SQJLXJk>_~S zvumu9L1Oi)uyEhqTNiLO<%4 zKMwdyV2SFiJo^(Cf4|&V#uJZ6tro;ae>tHf5mzRG=s4ZGW7+!VPz!<%o$xeWMR4Q5 z$xUtv8AGATyawBQbbfk!_}9`bN=YsQ8ZoIti8{EABER9GC-@o&BD?~ojK%zpJed&K zY3ov=V`0k)%6OF{$OdksMJx9b+rxyMnzDP(DNwL6TjCb75#HNeNX8SqXnLkJLxG6!bb-Phxy*!H zOzpD(7-%9lgTrb^ zDFo-MKDk)T!$jdE{;%f?hd;?E@TY*}V))PHTZ^zd64_pLPE|8O3!eUrkgU{LmHSWS zcceV-*DZR}af?HHIq?;!R0afUEq5Yuqz+B$HG4JCju-fvNn)=*wl3Yfczmob!PO|l zln%k(g3U&@th}Tnq~R3O1|f^O z+ASd;0Gf8s=HSTMl9Bb)dQ%W__Ndh&M%9&w0-?_BPd4%syZV>oVisl zt|p|^j5E$D_7n@5Sw&BZAC5?6jmpb4CNHbo;EmHCpcV@=AS0m}D0}o;skS1yr%Ekx zVM)-Ra#HNCZd)4rAGF%j0m}?bGL;gg$bp6GPLm_oQb>`Rf463>J2s5>u9b{qR2HV! zDCu{VsAKS*pD=5HWBgtYEHXpz$w0Bu`RsY(bY56T%|u)BCe6Ig_T!?>U-%mh^{Nb= z?Fq0PsD|@X=pz=Eey*p_$;Jro$jUMaXauQROw%OmX75(LPrPpA;oCN$D42c9Q4Z0B zQ98^lG$M3~A4yY)>5ObIe3^+aR6QHdR}<27Bnb|FlwYNUM!u!om5QN@I_9gVvm$waDk9}M_;itEs zmDLd&J2X|%+}^TIt`hm0Lm;w^D1?o^vgb=YrM#T!lM5Pk^uZhA#|^ZNQ+3}1Gq$3} z)ZrB52b6vVPIBdDJ$6CQsM??7aQ0fNr`8xRe9-?;BxPAQ9GL9qoymgJ(}W7lJqhlq zp!~e4@6r8?Uxl&T>f&_@`^?v$D;`7MAGxO#hF+`4Yx(fSv_uM$M%u!-@dJX2%Br6S)h7PH` za*A7T36%rT9H9l%13EtgC%5m?(Tw;h5$1c;UaGnzM`43RW66!&0-nrlD@T+J3O=tK zf`U`*j8Fg4+AVu=*sL#xzVz$0g89^#Zy zw@By5G$h_3HpO}@^O!z;Ha?c%%XngEg>T7!Yzl0_T&QPa{~97sV-0z+Or-c`jfM&6 zh#Cw0cAk|7G7U{>$aAAejo>DW+!Cj_4d>`ja+JA}sWR!yOg3Nr zzL0ZC^p^TbSY0q}p%ggD=n%&xVBsRG^p*Z%s>}s0VJeYvS*XbE)j5i2N)l>sR!ZG% zqAsnT^hVYX_ZSd=R@#oW_>?R9`fEk*=x+jAm@JhV*qjUQNICtqfFxT!N-Nl0J4 zDKHU?$Guk?{L7HiDKt@I$^6hRZsJ61KPwhE*YtsDiJ}Fl$6knXPQDVd=_m)sM z0Igs#3^}IJ(fd5nCot%PFOq}V{m%?Ym%96$B}tgl27auydg&%o-cRBob9m z3Fp|YZaJcPXKE&L)T8spVZl`%&0?)OZE3vV=%|OLv2*f#8MJ-cK-pVcE5(E~6F!l& z{wh|KU#qW#=A4FXBi_mqLv(QIM`EV&u-W2Ydhk^;s_Zn7tRG6#|18ks*sUtbrtW~F zCI*;oz8NA|ObrkrTVG$7j-<0ObaQ1F+80``)ff4PlUTlJ?L@0e;QE$@Wn{~)na}Up z73@&!awbwcim=7+$nC4&ExyWgT%eU@iaa*4;PY+5dmAGATy|!%`~eLGRS0sK*7SOB zgvsZ}3&*%A5+;A(T1{=DKHpI-GShN!7sUChCSSRs%9p193y7;H9olJqvrRw@g+3B^ zS+AJ-iLpa{S7O!`2M23F;F}@yWb<3j^F@WbUh~JJ}sI6N*@=w73A!in=(p-8$=I!ji5LG|kakX0L#Aa7*Wo!Cv-TV5jnu6q* zG4vxV{G4c3Dc{Y#>wfRK@S?Nqn0UM(sc;Oy4w+(%5|SjQCD}2epIu?Mho)bmfAG^~ zC&vnz1Q%4k+`37BubSyqAaVxFNB1!be9-&Z>BxYTaiX(N!lbZ;)lWJ0}SqfneD9ZKgrV3Q3`t z8PEMpd7dKs5gx^|zWCUTx?8MdcwANoZBFU4df~-dGSdfal6ZEs#qzpDTKEAxXO9|; zwmVZkbV-hV&ptiL#s0LMzj6{p-Po|8^EUOB9?OdOn|s{lLmO|LdcA3pIk{~11Gj_@ z(OA^g&nUQBSwp@#y8~0y|^SDA$0928@ z<n2;PFW9e&;r8H0R+-cU>1pf~l| zi~`=Ett*YsXC)Um5~pUZ+{sFR2{!xgQmH~VgNcN@OND9zYK0pj%WaiE4Jf@Pl{)Q( zgX@>&US(ESk~+Q&U2Z}ebm{R#r5IIa7Hj5ymDKNfUgk41yzsQr0LoZ$Woc4>A{>FN*t@tCXE`h|_MtuMc&H6tMeISimiz3ILZS zNcEeS4e+_SPIKx#K%@~bi#Y{{OlKVg3-v=&PN{$}`TTnvDT9<*w)Kj|iZQ!V30hcK zq!wsG^aNQnt@uuodSZ8Cwswh_i$fCYPhn^#j$;-}wW9%{lM{uq!+gNx*VU4Fjaent zg(S{$;rp70&(+)NJfiM49V}F<(j7TUNMuh0y&Y?=4j>hgsA{V#(w9&(6Ilkqn}Qx~ zP=dnq%|(&+i#G5icWfPqY#zbcb`9$*@Uwf^W_=fGdN1g-g5j87D-)M!Ch_E`p7NEk zHKAp&_k8o-y?393qg`^mhH2TwL;q3V0Q;%k)bsGeQdphqI;X&$&p`mb{H~duzSLP`Xp8vBuuTr6Zf_-t= zL#evFs4YWrrnys1QHajP9+Wgof|eIy5qOrwC>({bq%f)pvqL-Q`X9qxJO)D-$+9@vu_DuPVi{h zjc=7Cw#bQ+(H0>2QI7ttPQi=3K3Je1dX(0Yn^TDTV9dfB)RX<-o;XvxLr@ykM2e+C zsiRry^xXTD{K%9yVzxAkwB<@YN&sTZ5NySxWfO}4TZp*`V07U1cl$WD-PUo8kifHZ z5{5?6K9-*Vi0*2&89CXH1bT@K8#e?nc?mDN%|1?e8u;<1xuoFer}W~Q+WVfsFI^=y z&et5f=WA?3sl&+@QBf~`xvMCc2!J>JVax`<0c?1 z-OrjQQG#`kuTX(j-%yb!9!b8BtfC@y%|fef7GXgKTF?gv2~W z-xa2dqwl@%HQ<6V(N6H6wdLyhZZQu+%dy4SsLbL~3P$H${s>sVuTM%Dd z^#N3+O!=K(_=VXyH*i=Z-|YRͰg>B`3akf&eyjh5Y}_E#V3cZR;7>T8{xcC>jm zwWejye-U2y>GLJeaYDiTbBlMzh0n_fw+(adxImTqNq=tZk{S)TTOT-+Z z6RuI}iFqNaEa!U^=s}8=<%88fWJvD<6U)srxFo%GA&ijZ2cQ1Io;=Euy@)|-G-whY zVc1ARm=Q*D@GF{i%fFG>oSyy{rC!{VpN|56Y5>Jz+VsLIF*}tlAqtX+bbp<#TB(2aw%g4sp z)XYQ|pHS_rrOVj8l>IzL+Bm;G4}lJ-x_>;R36~Mz3Za+|`rGcW>wYQm@NfGytzXZ+ za6QACD~w|WJ?qpQvp6I@y=RR{8y+cgm~lcOp5v6*+fV48_xW%w&0akV-duu6mbL%; zm~4*vZD>RgOYG)-Ba|)I{kLA=tTwQGC5RbmutqJCp3?b$8Qk-?#=+nGO zHQqFYeXMQKuFXUN$k)GO(YB+JFFxW-WePod4br$$yo%qyc)+|g(_T5RNKtFIeG!^dQ}!HRhs8RH3DQCC$ zt5Z`^ukg(Nq)+39v$|5enPjCY3gdEk`yww*G=8hd)edrZlequR4HT<7FD_}LkQzlyan`RK-5&duSAs%WT{X;AIWJXabh%Jpqv@$o z8{(3by@mB~gYj@{ey&zrR_B{NT5^4`u*A^P|31qT$F)ANbmpoSZ6Dd_qpxrl@UE-w z@1Fls(*!;QJdI~h!5q&MWtB?wzU4}rGg&H^RN=fOLE~h6vQU<^ zmCNkQW5?c+-%TzE&kz*$o0oNa)K@{jXrg1kJ!-`6ym-PrC8#Q^H zyOa>lmXk;OsXq$2z;yrkO0@Qi8~{@*HI z@R)j4(E?Thw5x{(9Sl1?+vo_H!BI49=bk!u1qc3+d=x?A#Pw_MqZ<52Ov&@Fl0Gn1 zi?b^;sl7&}5)~edjw)EwFqoVc)167ted_%1r$aDFA=wfOAC~>$;k^d~)~e7Xeh~fr zAygdv&BUpSLSV*m?mzKDzXb;&9{kbCk6H#;oM7-LVT#A4xOV3!V^j*NccX=7jDcH8^5BtJRqgI}vS z=m9c4KUD;#6h)Fl){4Y+zz!Dq>_6)e0G(&}@X!@Q==u1|sa}k*KIcDTtoXF*eE}N) zjR&aQxgfXuQ}(L|d^dG9@8;67v|I+~DlP&I{8k=Tn|a}^85*SX%F zr$iH^=INoeKv$p2xq%0rn(INt^BoQOSEXOE3+ZljU8a#c{} z#>jNUMFetLSlRJeOzo5>X*0n5KOeWv=YbG6dEv3#3 z2Mm86nPZ5lBZ@>L$aIaFm_*2W61!#-c@($0sOmKD;R_;_27K9qWQ|a(oEMyhW-0}D zvcP3GzmH>vZ_)4+e82UX!O!E6$Pd3@h2+fq;M^S`DTfjo^$hHPr;XWGNO;@^iw+oX zrGgLHLV4KWVfW+^{~?5iUenEBN*@5LAvI4-=|l3u3rUP@8xl$C+=UU=o7H>~so