From 8931a295c637a59e042d385596de0854817b837c Mon Sep 17 00:00:00 2001 From: MichalKinas <113341662+MichalKinas@users.noreply.github.com> Date: Wed, 15 Jan 2025 11:34:03 +0100 Subject: [PATCH] [ACS-9102] Local ACS deployment for E2Es (#4324) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [ACS-9102] Local ACS deployment draft * [ACS-9102] Move ACS deployment to composite action * [ACS-9102] Remove obsolete checks * [ACS-9102] Proper secrets for ACS deployment * [ACS-9102] Add required shell property * [ACS-9102] Use fixed acs deployment version * [ACS-9102] Proper acs deployment tag * [ACS-9102] Add required shell property * [ACS-9102] Fix helm install params * [ACS-9102] Remove obsolete checkout * [ACS-9102] Use more powerful runner for E2Es * [ACS-9102] Introduce temp secrets * test curl localhost * skip n-1 matrix jobs * test ipv4 first * [ACS-9102] Adjust Playwright E2E host * [ACS-9102] Bring back matrix job * disable unnecessary acs components * debug ingress after tests run * [ACS-9102] Fix folder-rules test suite * [ACS-9102] Fix part of viewer test suite * [ACS-9102] Additional E2E fixes * [ACS-9102] fixes for e2es -> empty-list and search * [ACS-9102] more fixes for e2es and github actions artifacts added for easier debugging * [ACS-9102] removed artifacts from GHA * print all logs * [ACS-9102] Remove outdated secrets * [ACS-9102] Remove after-e2e action * do not wait for reindexing * test on latest runner * Always debug ingress logs * avoid sleep waiting for app startup * cleanup unnecessary action input type * fixup indent * test with latest acs alpha * Revert "do not wait for reindexing" This reverts commit 86ca54de33a6b5bf1da65202caac5798b5f88d51. * [ACS-9102] Exclude unstable test cases * [ACS-9102] Exclude unstable test cases * [ACS-9102] Exclude unstable test cases --------- Co-authored-by: Giovanni Toraldo Co-authored-by: Adam Świderski --- .github/acs-deployment-values-override.yaml | 21 ++++ .github/actions/after-e2e/action.yml | 9 -- .github/actions/deploy-local-acs/action.yml | 97 ++++++++++++++++ .github/actions/get-image-tag/action.yml | 1 - .github/actions/git-tag/action.yml | 3 - .github/actions/publish-libs/action.yml | 5 - .github/actions/run-e2e-playwright/action.yml | 12 +- .../update-library-versions/action.yml | 2 - .github/workflows/pull-request.yml | 33 ++++-- .../edit-actions/exclude.tests.json | 4 +- .../src/tests/edit-offline.e2e.ts | 2 +- .../src/tests/create-rules.e2e.ts | 41 ++++--- e2e/playwright/list-views/exclude.tests.json | 3 +- .../list-views/src/tests/empty-list.e2e.ts | 108 +++++++++--------- .../list-views/src/tests/permissions.e2e.ts | 9 +- .../list-views/src/tests/recent-files.e2e.ts | 9 +- .../list-views/src/tests/shared-files.e2e.ts | 10 +- e2e/playwright/search/exclude.tests.json | 3 +- .../search/src/tests/search-sorting.e2e.ts | 77 +++++-------- e2e/playwright/viewer/exclude.tests.json | 3 +- .../viewer/src/tests/viewer-action.e2e.ts | 9 +- .../viewer/src/tests/viewer-file-types.e2e.ts | 10 +- e2e/playwright/viewer/src/tests/viewer.e2e.ts | 44 ++++++- .../src/api/rules-api.ts | 11 +- .../components/actions-dropdown.component.ts | 5 + .../components/viewer.component.ts | 3 +- .../aca-playwright-shared/src/utils/utils.ts | 15 +++ 27 files changed, 361 insertions(+), 188 deletions(-) create mode 100644 .github/acs-deployment-values-override.yaml delete mode 100644 .github/actions/after-e2e/action.yml create mode 100644 .github/actions/deploy-local-acs/action.yml diff --git a/.github/acs-deployment-values-override.yaml b/.github/acs-deployment-values-override.yaml new file mode 100644 index 0000000000..0050db4f64 --- /dev/null +++ b/.github/acs-deployment-values-override.yaml @@ -0,0 +1,21 @@ +share: + enabled: false +postgresql-sync: + enabled: false +alfresco-sync-service: + enabled: false +alfresco-digital-workspace: + enabled: false +alfresco-control-center: + enabled: false +alfresco-ai-transformer: + enabled: false +elasticsearch-audit: + enabled: false +alfresco-audit-storage: + enabled: false +kibana-audit: + enabled: false +alfresco-repository: + image: + tag: 25.1.0-A.7 diff --git a/.github/actions/after-e2e/action.yml b/.github/actions/after-e2e/action.yml deleted file mode 100644 index a918a77aa4..0000000000 --- a/.github/actions/after-e2e/action.yml +++ /dev/null @@ -1,9 +0,0 @@ -name: "After e2e" -description: "Runs cleanup tasks after e2e run" - -runs: - using: "composite" - steps: - - name: Remove storage file - shell: bash - run: rm -f ./storage-state/AdminUserState.json diff --git a/.github/actions/deploy-local-acs/action.yml b/.github/actions/deploy-local-acs/action.yml new file mode 100644 index 0000000000..32fe75f5da --- /dev/null +++ b/.github/actions/deploy-local-acs/action.yml @@ -0,0 +1,97 @@ +name: deploy-local-acs +description: Deploy local ACS for E2E testing + +inputs: + docker_username: + description: 'Docker username' + required: true + docker_password: + description: 'Docker password' + required: true + quay_username: + description: 'Quay username' + required: true + quay_password: + description: 'Quay password' + required: true + +runs: + using: "composite" + steps: + - uses: azure/setup-helm@fe7b79cd5ee1e45176fcad797de68ecaf3ca4814 + with: + version: "3.14.3" + + - name: Login to Docker Hub + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + with: + username: ${{ inputs.docker_username }} + password: ${{ inputs.docker_password }} + + - name: Login to Quay.io + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + with: + registry: quay.io + username: ${{ inputs.quay_username }} + password: ${{ inputs.quay_password }} + + - name: Setup cluster + uses: Alfresco/alfresco-build-tools/.github/actions/setup-kind@v8.4.0 + with: + ingress-nginx-ref: controller-v1.8.2 + metrics: "true" + + - name: Set nginx ingress config + shell: bash + run: >- + kubectl -n ingress-nginx patch cm ingress-nginx-controller + -p '{"data": {"allow-snippet-annotations":"true"}}' + + - name: Create registries auth secret + shell: bash + run: >- + kubectl create secret generic regcred + --from-file=.dockerconfigjson=$HOME/.docker/config.json + --type=kubernetes.io/dockerconfigjson + + - name: Add dependency chart repos + shell: bash + run: | + helm repo add self https://alfresco.github.io/alfresco-helm-charts/ + helm repo add elastic https://helm.elastic.co/ + + - name: Checkout acs-deployment sources + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + repository: Alfresco/acs-deployment + ref: v8.6.1 + path: acs-deployment + + - name: Helm install + shell: bash + run: >- + helm dep build acs-deployment/helm/alfresco-content-services && + helm install acs acs-deployment/helm/alfresco-content-services + --set global.search.sharedSecret="$(openssl rand -hex 24)" + --set global.known_urls=http://localhost + --set global.alfrescoRegistryPullSecrets=regcred + --values acs-deployment/test/enterprise-integration-test-values.yaml + --values .github/acs-deployment-values-override.yaml + + - name: Watch Helm deployment + shell: bash + run: | + kubectl get pods --watch & + KWPID=$! + kubectl wait --timeout=7m --all=true --for=condition=Available deploy && kill $KWPID + echo -n "Waiting for ESC Reindexing job to complete... " + kubectl wait --timeout=5m --for=condition=complete job/acs-alfresco-search-enterprise-reindexing + echo "Completed." + + - name: Spit cluster status after install + if: always() + shell: bash + run: | + helm ls --all-namespaces --all + helm status acs --show-resources + kubectl describe pod diff --git a/.github/actions/get-image-tag/action.yml b/.github/actions/get-image-tag/action.yml index 8ee539109d..33a5020ac1 100644 --- a/.github/actions/get-image-tag/action.yml +++ b/.github/actions/get-image-tag/action.yml @@ -5,7 +5,6 @@ inputs: branch_name: description: 'Name of the branch the workflow runs on' required: true - type: string runs: using: "composite" diff --git a/.github/actions/git-tag/action.yml b/.github/actions/git-tag/action.yml index 99e0017058..0b37e5d17e 100644 --- a/.github/actions/git-tag/action.yml +++ b/.github/actions/git-tag/action.yml @@ -5,15 +5,12 @@ inputs: github_token: description: 'Github token' required: true - type: string branch_name: description: 'Name of the branch the workflow runs on' required: true - type: string dry-run: description: dry run flag required: true - type: boolean runs: using: "composite" diff --git a/.github/actions/publish-libs/action.yml b/.github/actions/publish-libs/action.yml index 8f308c77d4..a91cdff0e1 100644 --- a/.github/actions/publish-libs/action.yml +++ b/.github/actions/publish-libs/action.yml @@ -5,23 +5,18 @@ inputs: branch_name: description: 'Name of the branch the workflow runs on' required: true - type: string github_token: description: 'Github token' required: true - type: string npm_registry_token: description: 'NPM registry token' required: true - type: string npm_tag: description: 'NPM tag' required: true - type: string dry-run: description: dry run flag required: true - type: boolean runs: using: "composite" diff --git a/.github/actions/run-e2e-playwright/action.yml b/.github/actions/run-e2e-playwright/action.yml index f3b3efa7bc..be062b9708 100644 --- a/.github/actions/run-e2e-playwright/action.yml +++ b/.github/actions/run-e2e-playwright/action.yml @@ -5,16 +5,13 @@ inputs: options: description: 'Options' required: true - type: string test-runner: description: 'Test runner' required: false - type: string default: 'Playwright' artifact-name: description: Name of the artifact cache required: true - type: string runs: using: "composite" @@ -25,7 +22,12 @@ runs: run: | npm start > /dev/null &\ + printf "Waiting for the application to be ready..." + while ! curl -sf ${PLAYWRIGHT_E2E_HOST} > /dev/null; do + printf "." + sleep 1 + done + printf "\nApplication is ready.\n" + echo "Running playwright tests with options ${{ inputs.options }}" - sleep 90 npx nx run ${{ inputs.options }}-e2e:e2e - diff --git a/.github/actions/update-library-versions/action.yml b/.github/actions/update-library-versions/action.yml index 1251349d35..0f277608a7 100644 --- a/.github/actions/update-library-versions/action.yml +++ b/.github/actions/update-library-versions/action.yml @@ -5,11 +5,9 @@ inputs: branch_name: description: 'Name of the branch the workflow runs on' required: true - type: string dry-run: description: dry run flag required: true - type: boolean runs: using: "composite" diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 6cdbd0c7f0..c9263892c2 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -12,14 +12,14 @@ concurrency: cancel-in-progress: true env: - BASE_URL: ${{ secrets.PIPELINE_ENV_URL }} - ADMIN_EMAIL: ${{ secrets.PIPELINE_ADMIN_USERNAME }} - ADMIN_PASSWORD: ${{ secrets.PIPELINE_ADMIN_PASSWORD }} - HR_USER: ${{ secrets.HR_USER }} - HR_USER_PASSWORD: ${{ secrets.HR_USER_PASSWORD }} + BASE_URL: http://localhost + ADMIN_EMAIL: admin + ADMIN_PASSWORD: admin + HR_USER: admin + HR_USER_PASSWORD: admin SCREENSHOT_USERNAME: ${{ secrets.SCREENSHOT_USERNAME }} SCREENSHOT_PASSWORD: ${{ secrets.SCREENSHOT_PASSWORD}} - PLAYWRIGHT_E2E_HOST: ${{ secrets.PLAYWRIGHT_E2E_HOST }} + PLAYWRIGHT_E2E_HOST: http://localhost:4200 GH_BUILD_NUMBER: ${{ github.run_id }} REPORT_PORTAL_URL: ${{ secrets.REPORT_PORTAL_URL }} REPORT_PORTAL_TOKEN: ${{ secrets.REPORT_PORTAL_TOKEN }} @@ -105,7 +105,9 @@ jobs: e2es-playwright: needs: [lint, build, unit-tests] name: 'E2E Playwright - ${{ matrix.e2e-suites.name }}' - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 + env: + NODE_OPTIONS: --dns-result-order=ipv4first strategy: fail-fast: false matrix: @@ -165,18 +167,31 @@ jobs: path: ./dist/content-ce key: cache-dist-${{ github.run_id }} + - name: Deploy local ACS + uses: ./.github/actions/deploy-local-acs + with: + docker_username: ${{ secrets.DOCKER_USERNAME }} + docker_password: ${{ secrets.DOCKER_PASSWORD }} + quay_username: ${{ secrets.QUAY_USERNAME }} + quay_password: ${{ secrets.QUAY_PASSWORD }} + - name: Before e2e uses: ./.github/actions/before-e2e - - name: before playwright + - name: Before playwright shell: bash run: npx playwright install chromium + - uses: ./.github/actions/run-e2e-playwright with: options: "${{ matrix.e2e-suites.name }}" artifact-name: ${{ matrix.e2e-suites.name }} test-runner: playwright - - uses: ./.github/actions/after-e2e + + - name: Debug Ingress Controller Logs + if: always() + run: | + kubectl logs -n ingress-nginx -l app.kubernetes.io/name=ingress-nginx --tail=-1 finalize: if: ${{ always() }} diff --git a/e2e/playwright/edit-actions/exclude.tests.json b/e2e/playwright/edit-actions/exclude.tests.json index 0967ef424b..daf8ad8cf0 100644 --- a/e2e/playwright/edit-actions/exclude.tests.json +++ b/e2e/playwright/edit-actions/exclude.tests.json @@ -1 +1,3 @@ -{} +{ + "XAT-5304": "https://hyland.atlassian.net/browse/ACS-9154" +} diff --git a/e2e/playwright/edit-actions/src/tests/edit-offline.e2e.ts b/e2e/playwright/edit-actions/src/tests/edit-offline.e2e.ts index 844fb587e8..0bc83df68d 100644 --- a/e2e/playwright/edit-actions/src/tests/edit-offline.e2e.ts +++ b/e2e/playwright/edit-actions/src/tests/edit-offline.e2e.ts @@ -74,7 +74,7 @@ test.describe('Edit offline - on Personal Files', () => { await personalFiles.dataTable.selectItems(file1); await personalFiles.acaHeader.clickMoreActions(); await personalFiles.matMenu.clickMenuItem('Edit Offline'); - const [download] = await Promise.all([personalFiles.page.waitForEvent('download')]); + const [download] = await Promise.all([personalFiles.page.waitForEvent('download', { timeout: 5000 })]); expect(download.suggestedFilename()).toBe(file1); }); diff --git a/e2e/playwright/folder-rules/src/tests/create-rules.e2e.ts b/e2e/playwright/folder-rules/src/tests/create-rules.e2e.ts index 43e3d2d8ea..52c59418dc 100644 --- a/e2e/playwright/folder-rules/src/tests/create-rules.e2e.ts +++ b/e2e/playwright/folder-rules/src/tests/create-rules.e2e.ts @@ -131,24 +131,23 @@ test.describe('Folder Rules Actions', () => { test('[XAT-888] Create a rule with multiple actions', async ({ personalFiles, nodesPage }) => { const checkInValue = 'check In Value'; - const actionValue = ' A site which contains sfdc content [sfdc:site] '; - const autoDeclareOptionsValue = 'For all major and minor versions [ALL]'; + const actionValue = 'Site Container [st:siteContainer]'; + const specialiseTypeValue = 'Action Base Type [act:actionbase]'; const simpleWorkFlow = 'accept reject'; await personalFiles.navigate({ remoteUrl: `#/nodes/${randomFolderName1Id}/rules` }); await nodesPage.toolbar.clickCreateRuleButton(); await nodesPage.manageRulesDialog.ruleNameInputLocator.fill(randomRuleName); - await nodesPage.actionsDropdown.selectAction(ActionType.HideRecord, 0); - await nodesPage.actionsDropdown.selectAction(ActionType.IncrementCounter, 1); - await nodesPage.actionsDropdown.selectAction(ActionType.CheckIn, 2); - await nodesPage.actionsDropdown.insertCheckInActionValues(checkInValue, 2); - await nodesPage.actionsDropdown.selectAction(ActionType.AddAspect, 3); - await nodesPage.actionsDropdown.insertAddAspectActionValues(actionValue, 3); - await nodesPage.actionsDropdown.selectAction(ActionType.AutoDeclareOptions, 4); - await nodesPage.actionsDropdown.insertAutoDeclareOptionsActionValues(autoDeclareOptionsValue, 4); - await nodesPage.actionsDropdown.selectAction(ActionType.SimpleWorkflow, 5); - await nodesPage.actionsDropdown.insertSimpleWorkflowActionValues(simpleWorkFlow, 5); + await nodesPage.actionsDropdown.selectAction(ActionType.IncrementCounter, 0); + await nodesPage.actionsDropdown.selectAction(ActionType.CheckIn, 1); + await nodesPage.actionsDropdown.insertCheckInActionValues(checkInValue, 1); + await nodesPage.actionsDropdown.selectAction(ActionType.AddAspect, 2); + await nodesPage.actionsDropdown.insertAddAspectActionValues(actionValue, 2); + await nodesPage.actionsDropdown.selectAction(ActionType.SpecialiseType, 3); + await nodesPage.actionsDropdown.insertSpecialiseTypeActionValues(specialiseTypeValue, 3); + await nodesPage.actionsDropdown.selectAction(ActionType.SimpleWorkflow, 4); + await nodesPage.actionsDropdown.insertSimpleWorkflowActionValues(simpleWorkFlow, 4); await nodesPage.manageRulesDialog.createRuleButton.click(); @@ -185,18 +184,18 @@ test.describe('Folder Rules Actions', () => { await nodesPage.toolbar.clickCreateRuleButton(); await nodesPage.manageRulesDialog.ruleNameInputLocator.fill(randomRuleName); await nodesPage.actionsDropdown.selectAction(ActionType.AddAspect, 0); - await nodesPage.actionsDropdown.insertAddAspectActionValues('Controls', 0); + await nodesPage.actionsDropdown.insertAddAspectActionValues('Classifiable', 0); await nodesPage.actionsDropdown.selectAction(ActionType.AddAspect, 1); - await nodesPage.actionsDropdown.insertAddAspectActionValues('CMM', 1); + await nodesPage.actionsDropdown.insertAddAspectActionValues('Countable', 1); await nodesPage.actionsDropdown.selectAction(ActionType.AddAspect, 2); - await nodesPage.actionsDropdown.insertAddAspectActionValues('folder', 2); + await nodesPage.actionsDropdown.insertAddAspectActionValues('Incomplete', 2); await nodesPage.actionsDropdown.selectAction(ActionType.AddAspect, 3); - await nodesPage.actionsDropdown.insertAddAspectActionValues('site which', 3); + await nodesPage.actionsDropdown.insertAddAspectActionValues('Site container', 3); await nodesPage.manageRulesDialog.createRuleButton.click(); await nodesPage.manageRulesDialog.createRuleButton.waitFor({ state: 'hidden' }); await nodesPage.manageRules.getGroupsList(randomRuleName).click(); - await nodesPage.manageRules.checkAspects(['sc:controlsAreClearance', 'sfdc:objectModel', 'sfdc:folder', 'sfdc:site']); + await nodesPage.manageRules.checkAspects(['cm:generalclassifiable', 'cm:countable', 'sys:incomplete', 'st:siteContainer']); }); test('[XAT-891] Prevent rule creation after clicking on cancel during selecting destination folder', async ({ nodesPage, personalFiles }) => { @@ -204,7 +203,7 @@ test.describe('Folder Rules Actions', () => { await nodesPage.toolbar.clickCreateRuleButton(); await nodesPage.manageRulesDialog.ruleNameInputLocator.fill(randomRuleName); await nodesPage.actionsDropdown.selectAction(ActionType.AddAspect, 0); - await nodesPage.actionsDropdown.insertAddAspectActionValues('Controls', 0); + await nodesPage.actionsDropdown.insertAddAspectActionValues('Classifiable', 0); await expect(nodesPage.manageRulesDialog.createRuleButton).toBeEnabled(); await nodesPage.actionsDropdown.selectAction(ActionType.Copy, 1); await nodesPage.manageRulesDialog.destinationFolderButton.click(); @@ -217,7 +216,7 @@ test.describe('Folder Rules Actions', () => { await nodesPage.toolbar.clickCreateRuleButton(); await nodesPage.manageRulesDialog.ruleNameInputLocator.fill(randomRuleName); await nodesPage.actionsDropdown.selectAction(ActionType.AddAspect, 0); - await nodesPage.actionsDropdown.insertAddAspectActionValues('Controls', 0); + await nodesPage.actionsDropdown.insertAddAspectActionValues('Classifiable', 0); await expect(nodesPage.manageRulesDialog.createRuleButton).toBeEnabled(); await nodesPage.actionsDropdown.selectAction(ActionType.Copy, 1); await expect(nodesPage.manageRulesDialog.createRuleButton).toBeDisabled(); @@ -228,7 +227,7 @@ test.describe('Folder Rules Actions', () => { await nodesPage.toolbar.clickCreateRuleButton(); await nodesPage.manageRulesDialog.ruleNameInputLocator.fill(randomRuleName); await nodesPage.actionsDropdown.selectAction(ActionType.AddAspect, 0); - await nodesPage.actionsDropdown.insertAddAspectActionValues('Controls', 0); + await nodesPage.actionsDropdown.insertAddAspectActionValues('Classifiable', 0); await expect(nodesPage.manageRulesDialog.createRuleButton).toBeEnabled(); await nodesPage.actionsDropdown.insertAddAspectActionValues('None', 0); await expect(nodesPage.manageRulesDialog.createRuleButton).toBeDisabled(); @@ -239,7 +238,7 @@ test.describe('Folder Rules Actions', () => { await nodesPage.toolbar.clickCreateRuleButton(); await nodesPage.manageRulesDialog.ruleNameInputLocator.fill(randomRuleName); await nodesPage.actionsDropdown.selectAction(ActionType.AddAspect, 0); - await nodesPage.actionsDropdown.insertAddAspectActionValues('Controls', 0); + await nodesPage.actionsDropdown.insertAddAspectActionValues('Classifiable', 0); await expect(nodesPage.manageRulesDialog.createRuleButton).toBeEnabled(); await nodesPage.actionsDropdown.selectAction(ActionType.CheckIn, 1); await expect(nodesPage.manageRulesDialog.createRuleButton).toBeEnabled(); diff --git a/e2e/playwright/list-views/exclude.tests.json b/e2e/playwright/list-views/exclude.tests.json index 337469aaa3..1904f02da1 100644 --- a/e2e/playwright/list-views/exclude.tests.json +++ b/e2e/playwright/list-views/exclude.tests.json @@ -1,3 +1,4 @@ { - "C261153": "https://alfresco.atlassian.net/browse/AAE-7517" + "C261153": "https://alfresco.atlassian.net/browse/AAE-7517", + "C589205": "https://hyland.atlassian.net/browse/ACS-9154" } diff --git a/e2e/playwright/list-views/src/tests/empty-list.e2e.ts b/e2e/playwright/list-views/src/tests/empty-list.e2e.ts index af406c12fe..1258325475 100755 --- a/e2e/playwright/list-views/src/tests/empty-list.e2e.ts +++ b/e2e/playwright/list-views/src/tests/empty-list.e2e.ts @@ -80,35 +80,14 @@ test.describe('Empty list views', () => { async function openEmptyTab(searchPage: SearchPage, tab: string, emptyStateTitle: string, emptyStateSubtitle: string) { await searchPage.sidenav.openPanel(tab); - expect(await searchPage.dataTable.isEmpty()).toBeTruthy(); - expect(await searchPage.dataTable.getEmptyStateTitle()).toContain(emptyStateTitle); - expect(await searchPage.dataTable.getEmptyStateSubtitle()).toContain(emptyStateSubtitle); - } - - [ - { - tab: SIDEBAR_LABELS.FAVORITE_LIBRARIES, - id: 'C289911', - emptyStateTitle: `No Favorite Libraries`, - emptyStateSubtitle: 'Favorite a library that you want to find easily later.' - }, - { - tab: SIDEBAR_LABELS.RECENT_FILES, - id: 'C213169', - emptyStateTitle: 'No recent files', - emptyStateSubtitle: 'Items you uploaded or edited in the last 30 days are shown here.' - }, - { - tab: SIDEBAR_LABELS.FAVORITES, - id: 'C280133', - emptyStateTitle: 'No favorite files or folders', - emptyStateSubtitle: 'Favorite items that you want to easily find later.' + await searchPage.dataTable.spinnerWaitForReload(); + if (await searchPage.dataTable.isEmpty()) { + expect(await searchPage.dataTable.getEmptyStateTitle()).toContain(emptyStateTitle); + expect(await searchPage.dataTable.getEmptyStateSubtitle()).toContain(emptyStateSubtitle); + } else { + expect(await searchPage.dataTable.getRowsCount()).toEqual(1); } - ].forEach((testCase) => { - test(`[${testCase.id}] empty ${testCase.tab}`, async ({ searchPage }) => { - await openEmptyTab(searchPage, testCase.tab, testCase.emptyStateTitle, testCase.emptyStateSubtitle); - }); - }); + } async function checkPaginationForTabs(searchPage: SearchPage, tab: string, personalFiles: PersonalFilesPage) { await searchPage.sidenav.openPanel(tab); @@ -120,34 +99,49 @@ test.describe('Empty list views', () => { expect(await personalFiles.pagination.isNextButtonPresent()).toBeFalsy(); } - [ - { - tab: SIDEBAR_LABELS.FAVORITES, - id: 'C280111' - }, - { - tab: SIDEBAR_LABELS.MY_LIBRARIES, - id: 'C280084' - }, - { - tab: SIDEBAR_LABELS.FAVORITE_LIBRARIES, - id: 'C291873' - }, - { - tab: SIDEBAR_LABELS.PERSONAL_FILES, - id: 'C280075' - }, - { - tab: SIDEBAR_LABELS.RECENT_FILES, - id: 'C280102' - }, - { - tab: SIDEBAR_LABELS.TRASH, - id: 'C280120' - } - ].forEach((testCase) => { - test(`[${testCase.id}] ${testCase.tab} - pagination controls not displayed`, async ({ searchPage, personalFiles }) => { - await checkPaginationForTabs(searchPage, testCase.tab, personalFiles); - }); + test(`[C289911] empty ${SIDEBAR_LABELS.FAVORITE_LIBRARIES}`, async ({ searchPage }) => { + await openEmptyTab( + searchPage, + SIDEBAR_LABELS.FAVORITE_LIBRARIES, + 'No Favorite Libraries', + 'Favorite a library that you want to find easily later.' + ); + }); + + test(`[C213169] empty ${SIDEBAR_LABELS.RECENT_FILES}`, async ({ searchPage }) => { + await openEmptyTab( + searchPage, + SIDEBAR_LABELS.RECENT_FILES, + 'No recent files', + 'Items you uploaded or edited in the last 30 days are shown here.' + ); + }); + + test(`[C280133] empty ${SIDEBAR_LABELS.FAVORITES}`, async ({ searchPage }) => { + await openEmptyTab(searchPage, SIDEBAR_LABELS.FAVORITES, 'No favorite files or folders', 'Favorite items that you want to easily find later.'); + }); + + test(`[C280111] ${SIDEBAR_LABELS.FAVORITES} - pagination controls not displayed`, async ({ searchPage, personalFiles }) => { + await checkPaginationForTabs(searchPage, SIDEBAR_LABELS.FAVORITES, personalFiles); + }); + + test(`[C280084] ${SIDEBAR_LABELS.MY_LIBRARIES} - pagination controls not displayed`, async ({ searchPage, personalFiles }) => { + await checkPaginationForTabs(searchPage, SIDEBAR_LABELS.MY_LIBRARIES, personalFiles); + }); + + test(`[C291873] ${SIDEBAR_LABELS.FAVORITE_LIBRARIES} - pagination controls not displayed`, async ({ searchPage, personalFiles }) => { + await checkPaginationForTabs(searchPage, SIDEBAR_LABELS.FAVORITE_LIBRARIES, personalFiles); + }); + + test(`[C280075] ${SIDEBAR_LABELS.PERSONAL_FILES} - pagination controls not displayed`, async ({ searchPage, personalFiles }) => { + await checkPaginationForTabs(searchPage, SIDEBAR_LABELS.PERSONAL_FILES, personalFiles); + }); + + test(`[C280102] ${SIDEBAR_LABELS.RECENT_FILES} - pagination controls not displayed`, async ({ searchPage, personalFiles }) => { + await checkPaginationForTabs(searchPage, SIDEBAR_LABELS.RECENT_FILES, personalFiles); + }); + + test(`[C280120] ${SIDEBAR_LABELS.TRASH} - pagination controls not displayed`, async ({ searchPage, personalFiles }) => { + await checkPaginationForTabs(searchPage, SIDEBAR_LABELS.TRASH, personalFiles); }); }); diff --git a/e2e/playwright/list-views/src/tests/permissions.e2e.ts b/e2e/playwright/list-views/src/tests/permissions.e2e.ts index 345aa4e52f..a77f2417d5 100755 --- a/e2e/playwright/list-views/src/tests/permissions.e2e.ts +++ b/e2e/playwright/list-views/src/tests/permissions.e2e.ts @@ -43,7 +43,14 @@ test.describe('Special permissions', () => { test.beforeAll(async () => { const apiClientFactory = new ApiClientFactory(); await apiClientFactory.setUpAcaBackend('admin'); - await apiClientFactory.createUser({ username }); + + try { + await apiClientFactory.createUser({ username }); + } catch (exception) { + if (JSON.parse(exception.message).error.statusCode !== 409) { + throw new Error(`----- beforeAll failed : ${exception}`); + } + } }); test.describe('file not displayed if user no longer has permissions on it', () => { diff --git a/e2e/playwright/list-views/src/tests/recent-files.e2e.ts b/e2e/playwright/list-views/src/tests/recent-files.e2e.ts index 99cd9b00ab..4c59c730b9 100755 --- a/e2e/playwright/list-views/src/tests/recent-files.e2e.ts +++ b/e2e/playwright/list-views/src/tests/recent-files.e2e.ts @@ -45,7 +45,14 @@ test.describe('Recent Files', () => { test.setTimeout(timeouts.extendedTest); const apiClientFactory = new ApiClientFactory(); await apiClientFactory.setUpAcaBackend('admin'); - await apiClientFactory.createUser({ username }); + + try { + await apiClientFactory.createUser({ username }); + } catch (exception) { + if (JSON.parse(exception.message).error.statusCode !== 409) { + throw new Error(`----- beforeAll failed : ${exception}`); + } + } nodeActionsUser = await NodesApi.initialize(username, username); siteActionsUser = await SitesApi.initialize(username, username); trashcanApi = await TrashcanApi.initialize(username, username); diff --git a/e2e/playwright/list-views/src/tests/shared-files.e2e.ts b/e2e/playwright/list-views/src/tests/shared-files.e2e.ts index e762d4c080..48a611cd32 100644 --- a/e2e/playwright/list-views/src/tests/shared-files.e2e.ts +++ b/e2e/playwright/list-views/src/tests/shared-files.e2e.ts @@ -44,7 +44,15 @@ test.describe('Shared Files', () => { test.setTimeout(timeouts.extendedTest); const apiClientFactory = new ApiClientFactory(); await apiClientFactory.setUpAcaBackend('admin'); - await apiClientFactory.createUser({ username }); + + try { + await apiClientFactory.createUser({ username }); + } catch (exception) { + if (JSON.parse(exception.message).error.statusCode !== 409) { + throw new Error(`----- beforeAll failed : ${exception}`); + } + } + siteActionsAdmin = await SitesApi.initialize('admin'); const nodesApiAdmin = await NodesApi.initialize('admin'); const shareActionsAdmin = await SharedLinksApi.initialize('admin'); diff --git a/e2e/playwright/search/exclude.tests.json b/e2e/playwright/search/exclude.tests.json index 931f8c3508..7f72bcb98c 100644 --- a/e2e/playwright/search/exclude.tests.json +++ b/e2e/playwright/search/exclude.tests.json @@ -2,5 +2,6 @@ "C290019": "https://hyland.atlassian.net/browse/ACS-6928", "C290018": "https://hyland.atlassian.net/browse/ACS-6928", "C699046-3": "https://hyland.atlassian.net/browse/ACS-7464", - "C699498": "https://hyland.atlassian.net/browse/ACS-7682" + "C699498": "https://hyland.atlassian.net/browse/ACS-7682", + "C277736": "https://hyland.atlassian.net/browse/ACS-9154" } diff --git a/e2e/playwright/search/src/tests/search-sorting.e2e.ts b/e2e/playwright/search/src/tests/search-sorting.e2e.ts index 3969a7c479..dc2c75ec50 100644 --- a/e2e/playwright/search/src/tests/search-sorting.e2e.ts +++ b/e2e/playwright/search/src/tests/search-sorting.e2e.ts @@ -107,61 +107,38 @@ test.describe('Search sorting', () => { expectedFirstFile: string, expectedSecondFile: string ) { - await searchPage.searchWithin(`search-sort *${random}`, 'files'); - + await searchPage.searchWithin(`*${random}*`, 'files'); await searchPage.searchSortingPicker.sortBy(sortBy, sortOrder); - expect(await searchPage.dataTable.getNthRow(0).textContent()).toContain(expectedFirstFile); expect(await searchPage.dataTable.getNthRow(1).textContent()).toContain(expectedSecondFile); } - [ - { - column: 'Name', - id: 'C277728', - firstFile: fileJpg.name, - secondFile: filePdf.name - }, - { - column: 'Type', - id: 'C277740', - firstFile: filePdf.name, - secondFile: fileJpg.name - }, - { - column: 'Size', - id: 'C277738', - firstFile: filePdf.name, - secondFile: fileJpg.name - }, - { - column: 'Created date', - id: 'C277734', - firstFile: fileJpg.name, - secondFile: filePdf.name - }, - { - column: 'Modified date', - id: 'C277736', - firstFile: fileJpg.name, - secondFile: filePdf.name - }, - { - column: 'Relevance', - id: 'C277727', - firstFile: fileJpg.name, - secondFile: filePdf.name - }, - { - column: 'Modifier', - id: 'C277732', - firstFile: fileJpg.name, - secondFile: filePdf.name - } - ].forEach((testCase) => { - test(`[${testCase.id}] Sort by ${testCase.column}`, async ({ searchPage }) => { - await testSearchSorting(searchPage, testCase.column as SortByType, 'asc', testCase.firstFile, testCase.secondFile); - }); + test(`[C277728] Sort by Name`, async ({ searchPage }) => { + await testSearchSorting(searchPage, 'Name' as SortByType, 'asc', fileJpg.name, filePdf.name); + }); + + test(`[C277740] Sort by Type`, async ({ searchPage }) => { + await testSearchSorting(searchPage, 'Type' as SortByType, 'asc', filePdf.name, fileJpg.name); + }); + + test(`[C277738] Sort by Size`, async ({ searchPage }) => { + await testSearchSorting(searchPage, 'Size' as SortByType, 'asc', filePdf.name, fileJpg.name); + }); + + test(`[C277734] Sort by Created date`, async ({ searchPage }) => { + await testSearchSorting(searchPage, 'Created date' as SortByType, 'asc', fileJpg.name, filePdf.name); + }); + + test(`[C277736] Sort by Modified date`, async ({ searchPage }) => { + await testSearchSorting(searchPage, 'Modified date' as SortByType, 'asc', fileJpg.name, filePdf.name); + }); + + test(`[C277727] Sort by Relevance`, async ({ searchPage }) => { + await testSearchSorting(searchPage, 'Relevance' as SortByType, 'asc', fileJpg.name, filePdf.name); + }); + + test(`[C277732] Sort by Modifier`, async ({ searchPage }) => { + await testSearchSorting(searchPage, 'Modifier' as SortByType, 'asc', fileJpg.name, filePdf.name); }); test('[C277722] Sorting options are displayed', async ({ searchPage }) => { diff --git a/e2e/playwright/viewer/exclude.tests.json b/e2e/playwright/viewer/exclude.tests.json index 9147f5c5b9..d87b45de1a 100644 --- a/e2e/playwright/viewer/exclude.tests.json +++ b/e2e/playwright/viewer/exclude.tests.json @@ -2,5 +2,6 @@ "XAT-17181": "https://hyland.atlassian.net/browse/ACS-8865", "XAT-17182": "https://hyland.atlassian.net/browse/ACS-8865", "XAT-17184": "https://hyland.atlassian.net/browse/ACS-8865", - "XAT-17185": "https://hyland.atlassian.net/browse/ACS-8865" + "XAT-17185": "https://hyland.atlassian.net/browse/ACS-8865", + "MNT-21058": "https://hyland.atlassian.net/browse/ACS-9154" } diff --git a/e2e/playwright/viewer/src/tests/viewer-action.e2e.ts b/e2e/playwright/viewer/src/tests/viewer-action.e2e.ts index ceda713cf1..de1103aa98 100644 --- a/e2e/playwright/viewer/src/tests/viewer-action.e2e.ts +++ b/e2e/playwright/viewer/src/tests/viewer-action.e2e.ts @@ -181,7 +181,7 @@ test.describe('viewer action file', () => { test('[C297586] [C307004] Upload new version action - major', async ({ personalFiles, nodesApiAction }) => { await personalFiles.dataTable.performClickFolderOrFileToOpen(filePersonalFiles); - await personalFiles.viewer.waitForViewerToOpen(); + await personalFiles.viewer.waitForViewerToOpen('wait for viewer content'); await Utils.uploadFileNewVersion(personalFiles, docxFile2); @@ -191,7 +191,9 @@ test.describe('viewer action file', () => { await personalFiles.uploadNewVersionDialog.uploadButton.waitFor({ state: 'detached' }); await expect(personalFiles.uploadNewVersionDialog.cancelButton).toHaveCount(0); expect(await personalFiles.viewer.isViewerOpened(), 'Viewer is not open').toBe(true); - expect(await personalFiles.viewer.fileTitleButtonLocator.innerText()).toContain(docxFile2); + await Utils.waitForApiResponse(personalFiles, 'content', 200); + + expect(await personalFiles.viewer.getFileTitle()).toContain(docxFile2); expect(await nodesApiAction.getNodeProperty(filePersonalFilesId, 'cm:versionType'), 'File has incorrect version type').toEqual('MAJOR'); expect(await nodesApiAction.getNodeProperty(filePersonalFilesId, 'cm:versionLabel'), 'File has incorrect version label').toEqual('2.0'); }); @@ -207,7 +209,8 @@ test.describe('viewer action file', () => { await expect(personalFiles.uploadNewVersionDialog.cancelButton).toHaveCount(0); await personalFiles.viewer.waitForViewerToOpen(); - expect(await personalFiles.viewer.fileTitleButtonLocator.innerText()).toContain(docxFile); + await Utils.waitForApiResponse(personalFiles, 'content', 200); + expect(await personalFiles.viewer.getFileTitle()).toContain(docxFile); await personalFiles.acaHeader.clickViewerMoreActions(); await expect(personalFiles.matMenu.getMenuItemFromHeaderMenu('Cancel Editing'), `'Cancel Editing' button shouldn't be shown`).toBeHidden(); diff --git a/e2e/playwright/viewer/src/tests/viewer-file-types.e2e.ts b/e2e/playwright/viewer/src/tests/viewer-file-types.e2e.ts index 6c84e4e360..a1ee849d84 100644 --- a/e2e/playwright/viewer/src/tests/viewer-file-types.e2e.ts +++ b/e2e/playwright/viewer/src/tests/viewer-file-types.e2e.ts @@ -45,7 +45,15 @@ test.describe('viewer file types', () => { test.beforeAll(async () => { const apiClientFactory = new ApiClientFactory(); await apiClientFactory.setUpAcaBackend('admin'); - await apiClientFactory.createUser({ username }); + + try { + await apiClientFactory.createUser({ username }); + } catch (exception) { + if (JSON.parse(exception.message).error.statusCode !== 409) { + throw new Error(`----- beforeAll failed : ${exception}`); + } + } + nodesApi = await NodesApi.initialize(username, username); fileActionApi = await FileActionsApi.initialize(username, username); trashcanApi = await TrashcanApi.initialize(username, username); diff --git a/e2e/playwright/viewer/src/tests/viewer.e2e.ts b/e2e/playwright/viewer/src/tests/viewer.e2e.ts index 2283de30ae..f7f97a2efd 100644 --- a/e2e/playwright/viewer/src/tests/viewer.e2e.ts +++ b/e2e/playwright/viewer/src/tests/viewer.e2e.ts @@ -60,7 +60,13 @@ test.describe('viewer file', () => { const randomFolderName = `viewer-${Utils.random()}`; const apiClientFactory = new ApiClientFactory(); await apiClientFactory.setUpAcaBackend('admin'); - await apiClientFactory.createUser({ username }); + try { + await apiClientFactory.createUser({ username }); + } catch (exception) { + if (JSON.parse(exception.message).error.statusCode !== 409) { + throw new Error(`----- beforeAll failed : ${exception}`); + } + } nodesApi = await NodesApi.initialize(username, username); const fileActionApi = await FileActionsApi.initialize(username, username); trashcanApi = await TrashcanApi.initialize(username, username); @@ -77,13 +83,41 @@ test.describe('viewer file', () => { await shareActions.shareFileById(fileDocxId); await favoritesActions.addFavoriteById('file', fileDocxId); - await siteActionsAdmin.createSite(siteAdmin, Site.VisibilityEnum.PRIVATE); + try { + await siteActionsAdmin.createSite(siteAdmin, Site.VisibilityEnum.PRIVATE); + } catch (exception) { + if (JSON.parse(exception.message).error.statusCode !== 409) { + throw new Error(`----- beforeAll failed : ${exception}`); + } + } + docLibId = await siteActionsAdmin.getDocLibId(siteAdmin); - fileAdminId = (await fileActionApiAdmin.uploadFile(TEST_FILES.DOCX.path, fileAdmin, docLibId)).entry.id; - await siteActionsUser.createSite(siteUser, Site.VisibilityEnum.PUBLIC); + try { + fileAdminId = (await fileActionApiAdmin.uploadFile(TEST_FILES.DOCX.path, fileAdmin, docLibId)).entry.id; + } catch (exception) { + if (JSON.parse(exception.message).error.statusCode !== 409) { + throw new Error(`----- beforeAll failed : ${exception}`); + } + } + + try { + await siteActionsUser.createSite(siteUser, Site.VisibilityEnum.PUBLIC); + } catch (exception) { + if (JSON.parse(exception.message).error.statusCode !== 409) { + throw new Error(`----- beforeAll failed : ${exception}`); + } + } + docLibSiteUserId = await siteActionsUser.getDocLibId(siteUser); - await fileActionApi.uploadFile(TEST_FILES.DOCX.path, fileInSite, docLibSiteUserId); + + try { + await fileActionApi.uploadFile(TEST_FILES.DOCX.path, fileInSite, docLibSiteUserId); + } catch (exception) { + if (JSON.parse(exception.message).error.statusCode !== 409) { + throw new Error(`----- beforeAll failed : ${exception}`); + } + } await Promise.all([ favoritesActions.isFavoriteWithRetry(username, fileDocxId, { expect: true }), diff --git a/projects/aca-playwright-shared/src/api/rules-api.ts b/projects/aca-playwright-shared/src/api/rules-api.ts index add9e79457..8754d3900b 100644 --- a/projects/aca-playwright-shared/src/api/rules-api.ts +++ b/projects/aca-playwright-shared/src/api/rules-api.ts @@ -147,19 +147,19 @@ export class RulesApi { { actionDefinitionId: 'add-features', params: { - 'aspect-name': 'sc:controlsAreClearance' + 'aspect-name': 'cm:auditable' } }, { actionDefinitionId: 'add-features', params: { - 'aspect-name': 'sfdc:objectModel' + 'aspect-name': 'app:configurable' } }, { actionDefinitionId: 'add-features', params: { - 'aspect-name': 'sfdc:folder' + 'aspect-name': 'exif:exif' } } ] @@ -224,10 +224,6 @@ export class ActionTypes { actionDefinitionId: 'specialise-type', params: { 'type-name': 'sys:base' } }); - static readonly RECORDABLEVERSION = new ActionTypes('RECORDABLEVERSION', { - actionDefinitionId: 'recordable-version-config', - params: { version: 'ALL' } - }); static readonly SETPROPERTYVALUE = new ActionTypes('SETPROPERTYVALUE', { actionDefinitionId: 'set-property-value', params: { property: 'dl:ganttPercentComplete', value: 'test' } @@ -235,7 +231,6 @@ export class ActionTypes { static readonly actions = [ ActionTypes.ADDFEATURES.value, ActionTypes.CHECKIN.value, - ActionTypes.RECORDABLEVERSION.value, ActionTypes.SPECIALISETYPE.value, ActionTypes.SETPROPERTYVALUE.value ]; diff --git a/projects/aca-playwright-shared/src/page-objects/components/actions-dropdown.component.ts b/projects/aca-playwright-shared/src/page-objects/components/actions-dropdown.component.ts index 5045916105..e1cc8fbef5 100644 --- a/projects/aca-playwright-shared/src/page-objects/components/actions-dropdown.component.ts +++ b/projects/aca-playwright-shared/src/page-objects/components/actions-dropdown.component.ts @@ -60,6 +60,7 @@ export class ActionsDropdownComponent extends BaseComponent { private actionAspectNameLocator = '[data-automation-id="header-aspect-name"] .adf-property-field'; private actionCheckInInputLocator = '[data-automation-id="header-description"] input'; private actionAutoDeclareLocator = '[data-automation-id="header-version"] mat-select'; + private actionSpecialiseTypeLocator = '[data-automation-id="header-type-name"] mat-select'; private actionSimpleWorkflowStepInputLocator = '[data-automation-id="header-approve-step"] input'; private actionSimpleWorkflowApproveFolderLocator = `[data-automation-id="header-approve-folder"] mat-icon`; private actionSimpleWorkflowActionChoiceLocator = '[data-automation-id="content-node-selector-actions-choose"]'; @@ -98,6 +99,10 @@ export class ActionsDropdownComponent extends BaseComponent { await this.dropdownSelection(autoDeclareOptionsValue, this.actionAutoDeclareLocator, index); } + async insertSpecialiseTypeActionValues(specialiseTypeValue: string, index: number): Promise { + await this.dropdownSelection(specialiseTypeValue, this.actionSpecialiseTypeLocator, index); + } + async insertSimpleWorkflowActionValues(stepValue: string, index: number): Promise { await this.ruleActionLocator.nth(index).locator(this.actionSimpleWorkflowStepInputLocator).fill(stepValue); await this.ruleActionLocator.nth(index).locator(this.actionSimpleWorkflowApproveFolderLocator).click(); diff --git a/projects/aca-playwright-shared/src/page-objects/components/viewer.component.ts b/projects/aca-playwright-shared/src/page-objects/components/viewer.component.ts index 65bf213fd3..f5893e0f19 100644 --- a/projects/aca-playwright-shared/src/page-objects/components/viewer.component.ts +++ b/projects/aca-playwright-shared/src/page-objects/components/viewer.component.ts @@ -61,7 +61,7 @@ export class ViewerComponent extends BaseComponent { async waitForViewerToOpen(waitForViewerContent?: 'wait for viewer content'): Promise { await this.viewerLocator.waitFor({ state: 'visible', timeout: timeouts.medium }); if (waitForViewerContent) { - await this.spinnerWaitForReload(); + await this.waitForViewerLoaderToFinish(); } } @@ -94,6 +94,7 @@ export class ViewerComponent extends BaseComponent { async getFileTitle(): Promise { await this.fileTitleButtonLocator.waitFor({ state: 'visible', timeout: timeouts.normal }); + await this.waitForViewerLoaderToFinish(); return this.fileTitleButtonLocator.textContent(); } diff --git a/projects/aca-playwright-shared/src/utils/utils.ts b/projects/aca-playwright-shared/src/utils/utils.ts index da0243319c..7d848697aa 100644 --- a/projects/aca-playwright-shared/src/utils/utils.ts +++ b/projects/aca-playwright-shared/src/utils/utils.ts @@ -179,4 +179,19 @@ export class Utils { static trimArrayElements(arr: string[]): string[] { return arr.map((element) => element.trim()); } + + /** + * Waits for a specific API response. + * + * @param page - The Playwright page object. + * @param urlSubstring - The substring to look for in the URL. + * @param statusCode - The expected status code of the response. + */ + static async waitForApiResponse( + contentPage: LoginPage | MyLibrariesPage | PersonalFilesPage | FavoritesLibrariesPage | SearchPage | SharedPage | TrashPage, + urlSubstring: string, + statusCode: number + ) { + await contentPage.page.waitForResponse((response) => response.url().includes(urlSubstring) && response.status() === statusCode); + } }