diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 1cab6d61e..3b08eff07 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -171,6 +171,8 @@ jobs: id: 12 - name: "search" id: 13 + - name: "upload-download-actions" + id: 14 steps: - name: Checkout uses: actions/checkout@v3 diff --git a/e2e/playwright/upload-download-actions/.eslintrc.json b/e2e/playwright/upload-download-actions/.eslintrc.json new file mode 100644 index 000000000..597571811 --- /dev/null +++ b/e2e/playwright/upload-download-actions/.eslintrc.json @@ -0,0 +1,26 @@ +{ + "extends": "../../../.eslintrc.json", + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts" + ], + "parserOptions": { + "project": [ + "e2e/playwright/upload-download-actions/tsconfig.e2e.json" + ], + "createDefaultProgram": true + }, + "plugins": [ + "rxjs", + "unicorn" + ], + "rules": { + "@typescript-eslint/no-floating-promises": "off" + } + } + ] +} diff --git a/e2e/playwright/upload-download-actions/exclude.tests.json b/e2e/playwright/upload-download-actions/exclude.tests.json new file mode 100644 index 000000000..9e26dfeeb --- /dev/null +++ b/e2e/playwright/upload-download-actions/exclude.tests.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/e2e/playwright/upload-download-actions/playwright.config.ts b/e2e/playwright/upload-download-actions/playwright.config.ts new file mode 100644 index 000000000..243dace7b --- /dev/null +++ b/e2e/playwright/upload-download-actions/playwright.config.ts @@ -0,0 +1,44 @@ +/*! + * Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * Alfresco Example Content Application + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * from Hyland Software. If not, see <http://www.gnu.org/licenses/>. + */ + +import { PlaywrightTestConfig } from '@playwright/test'; +import { CustomConfig, getGlobalConfig, getExcludedTestsRegExpArray } from '@alfresco/playwright-shared'; +import EXCLUDED_JSON from './exclude.tests.json'; + +const config: PlaywrightTestConfig<CustomConfig> = { + ...getGlobalConfig, + + grepInvert: getExcludedTestsRegExpArray(EXCLUDED_JSON, 'Upload Download Actions'), + projects: [ + { + name: 'Upload Download Actions', + testDir: './src/tests', + use: { + users: [] + } + } + ] +}; + +export default config; diff --git a/e2e/playwright/upload-download-actions/project.json b/e2e/playwright/upload-download-actions/project.json new file mode 100644 index 000000000..5f74836e3 --- /dev/null +++ b/e2e/playwright/upload-download-actions/project.json @@ -0,0 +1,22 @@ +{ + "name": "upload-download-actions-e2e", + "$schema": "../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "e2e/playwright/upload-download-actions", + "projectType": "application", + "targets": { + "e2e": { + "executor": "nx:run-commands", + "options": { + "commands": ["npx playwright test --config=e2e/playwright/upload-download-actions/playwright.config.ts"] + }, + "configurations": { + "production": { + "devServerTarget": "content-ce:serve:production" + } + } + }, + "lint": { + "executor": "@angular-eslint/builder:lint" + } + } +} diff --git a/e2e/playwright/upload-download-actions/src/tests/download.e2e.ts b/e2e/playwright/upload-download-actions/src/tests/download.e2e.ts new file mode 100644 index 000000000..9f28d2255 --- /dev/null +++ b/e2e/playwright/upload-download-actions/src/tests/download.e2e.ts @@ -0,0 +1,85 @@ +/*! + * Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * Alfresco Example Content Application + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * from Hyland Software. If not, see <http://www.gnu.org/licenses/>. + */ + +import { expect } from '@playwright/test'; +import { ApiClientFactory, Utils, test, TrashcanApi, NodesApi } from '@alfresco/playwright-shared'; + +test.describe('Download from Personal Files', () => { + let trashcanApi: TrashcanApi; + let nodesApi: NodesApi; + let parentId: string; + + const random = Utils.random(); + const username = `user-${random}`; + const parent = `parent-${random}`; + const childFile = `childFile-${random}.txt`; + const childFolder = `childFolder-${random}`; + + test.beforeAll(async () => { + try { + const apiClientFactory = new ApiClientFactory(); + await apiClientFactory.setUpAcaBackend('admin'); + await apiClientFactory.createUser({ username }); + trashcanApi = await TrashcanApi.initialize(username, username); + nodesApi = await NodesApi.initialize(username, username); + + parentId = (await nodesApi.createFolder(parent)).entry.id; + await nodesApi.createFolder(childFolder, parentId); + await nodesApi.createFile(childFile, parentId); + } catch (error) { + console.error(`beforeAll failed: ${error}`); + } + }); + + test.beforeEach(async ({ loginPage }) => { + await Utils.tryLoginUser(loginPage, username, username, 'beforeEach failed'); + }); + + test.afterAll(async () => { + await Utils.deleteNodesSitesEmptyTrashcan(nodesApi, trashcanApi, 'afterAll failed'); + }); + + test('Download a file', async ({ personalFiles }) => { + await personalFiles.dataTable.performClickFolderOrFileToOpen(parent); + await personalFiles.dataTable.selectItem(childFile); + const [download] = await Promise.all([personalFiles.page.waitForEvent('download'), personalFiles.acaHeader.downloadButton.click()]); + expect(download.suggestedFilename()).toBe(childFile); + }); + + test('Download a folder', async ({ personalFiles }) => { + await personalFiles.dataTable.performClickFolderOrFileToOpen(parent); + await personalFiles.dataTable.selectItem(childFolder); + const [download] = await Promise.all([personalFiles.page.waitForEvent('download'), personalFiles.acaHeader.downloadButton.click()]); + const filePath = await download.path(); + expect(await Utils.verifyZipFileContent(filePath, [childFolder])).toBe(true); + }); + + test('Download multiple items', async ({ personalFiles }) => { + await personalFiles.dataTable.performClickFolderOrFileToOpen(parent); + await personalFiles.dataTable.selectMultiItem(childFile, childFolder); + const [download] = await Promise.all([personalFiles.page.waitForEvent('download'), personalFiles.acaHeader.downloadButton.click()]); + const filePath = await download.path(); + expect(await Utils.verifyZipFileContent(filePath, [childFile, childFolder])).toBe(true); + }); +}); diff --git a/e2e/playwright/upload-download-actions/tsconfig.e2e.adf.json b/e2e/playwright/upload-download-actions/tsconfig.e2e.adf.json new file mode 100644 index 000000000..87cbcf775 --- /dev/null +++ b/e2e/playwright/upload-download-actions/tsconfig.e2e.adf.json @@ -0,0 +1,15 @@ +{ + "extends": "../../../tsconfig.adf.json", + "compilerOptions": { + "outDir": "../../out-tsc/e2e", + "baseUrl": "./", + "module": "commonjs", + "target": "es2017", + "types": ["jasmine", "jasminewd2", "node"], + "skipLibCheck": true, + "paths": { + "@alfresco/playwright-shared": ["../../../projects/aca-playwright-shared/src/index.ts"] + } + }, + "exclude": ["node_modules"] +} diff --git a/e2e/playwright/upload-download-actions/tsconfig.e2e.json b/e2e/playwright/upload-download-actions/tsconfig.e2e.json new file mode 100755 index 000000000..c31798523 --- /dev/null +++ b/e2e/playwright/upload-download-actions/tsconfig.e2e.json @@ -0,0 +1,15 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "outDir": "../../out-tsc/e2e", + "baseUrl": "./", + "module": "commonjs", + "target": "es2017", + "types": ["jasmine", "jasminewd2", "node", "@playwright/test"], + "skipLibCheck": true, + "paths": { + "@alfresco/playwright-shared": ["../../../projects/aca-playwright-shared/src/index.ts"] + } + }, + "exclude": ["node_modules"] +} diff --git a/e2e/playwright/viewer/src/tests/viewer-action.e2e.ts b/e2e/playwright/viewer/src/tests/viewer-action.e2e.ts index c3deb981f..8bbc0ef83 100644 --- a/e2e/playwright/viewer/src/tests/viewer-action.e2e.ts +++ b/e2e/playwright/viewer/src/tests/viewer-action.e2e.ts @@ -85,7 +85,7 @@ test.describe('viewer action file', () => { await personalFiles.dataTable.performClickFolderOrFileToOpen(randomDocxName); await personalFiles.viewer.waitForViewerToOpen(); const downloadPromise = personalFiles.page.waitForEvent('download'); - await personalFiles.acaHeader.downloadButton.click(); + await personalFiles.acaHeader.downloadButtonViewer.click(); const download = await downloadPromise; expect(download.suggestedFilename()).toBe(randomDocxName); }); @@ -112,7 +112,7 @@ test.describe('viewer action file', () => { await personalFiles.matMenu.clickMenuItem('Edit Offline'); const downloadPromise = personalFiles.page.waitForEvent('download'); - await personalFiles.acaHeader.downloadButton.click(); + await personalFiles.acaHeader.downloadButtonViewer.click(); const download = await downloadPromise; expect(download.suggestedFilename(), 'File should found in download location').toBe(fileForEditOffline); expect(await personalFiles.viewer.isViewerOpened(), 'Viewer is closed after pressing Full screen').toBe(true); diff --git a/projects/aca-playwright-shared/src/page-objects/components/aca-header.component.ts b/projects/aca-playwright-shared/src/page-objects/components/aca-header.component.ts index b47c11aa8..f223f7e9f 100644 --- a/projects/aca-playwright-shared/src/page-objects/components/aca-header.component.ts +++ b/projects/aca-playwright-shared/src/page-objects/components/aca-header.component.ts @@ -36,7 +36,8 @@ export class AcaHeader extends BaseComponent { public searchButton = this.getChild('button[title="Search"]'); public fullScreenButton = this.getChild('button[id="app.viewer.fullscreen"]'); public shareButton = this.getChild('button[id="share-action-button"]'); - public downloadButton = this.getChild('button[id="app.viewer.download"]'); + public downloadButtonViewer = this.getChild('button[id="app.viewer.download"]'); + public downloadButton = this.getChild('button[id="app.toolbar.download"]'); public sharedDownloadButton = this.getChild('button[id="app.viewer.shared.download"]'); constructor(page: Page) { diff --git a/projects/aca-playwright-shared/src/utils/utils.ts b/projects/aca-playwright-shared/src/utils/utils.ts index 8050f144d..0a7783e57 100644 --- a/projects/aca-playwright-shared/src/utils/utils.ts +++ b/projects/aca-playwright-shared/src/utils/utils.ts @@ -27,6 +27,7 @@ import * as path from 'path'; import { LoginPage, MyLibrariesPage, PersonalFilesPage, FavoritesLibrariesPage, SearchPage, SharedPage, TrashPage } from '../'; import { NodesApi, TrashcanApi, SitesApi } from '@alfresco/playwright-shared'; import { format, subDays, subMonths, endOfMonth } from 'date-fns'; +import StreamZip from 'node-stream-zip'; export class Utils { static string257Long = 'x'.repeat(257); @@ -146,4 +147,26 @@ export class Utils { return { currentDate: formattedDate, previousDate: formattedDate2 }; } + + static async verifyZipFileContent(filePath: string, fileOrFolderName: string[]): Promise<boolean> { + const zip = new StreamZip({ + file: filePath, + storeEntries: true + }); + + return new Promise<boolean>((resolve) => { + zip.on('ready', () => { + const entries = zip.entries(); + const found = this.isFileOrFolderInEntries(entries, fileOrFolderName); + zip.close(); + resolve(found); + }); + }); + } + + private static isFileOrFolderInEntries(entries: { [name: string]: StreamZip.ZipEntry }, fileOrFolderName: string[]): boolean { + return fileOrFolderName.some((name) => { + return Object.keys(entries).some((entry) => entry.includes(name)); + }); + } }