diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index ffe12acc82..116b7e375d 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -109,7 +109,7 @@ jobs: fail-fast: false matrix: e2e-suites: - - name: "authentication,listViews,navigation,application" + - name: "listViews,navigation,application" id: 1 - name: "search" id: 2 @@ -178,6 +178,8 @@ jobs: id: 2 - name: "viewer" id: 3 + - name: "authentication" + id: 4 steps: - name: Checkout uses: actions/checkout@v3 diff --git a/e2e/playwright/authentication/.eslintrc.json b/e2e/playwright/authentication/.eslintrc.json new file mode 100644 index 0000000000..0e3d5cf2ef --- /dev/null +++ b/e2e/playwright/authentication/.eslintrc.json @@ -0,0 +1,26 @@ +{ + "extends": "../../../.eslintrc.json", + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts" + ], + "parserOptions": { + "project": [ + "e2e/playwright/authentication/tsconfig.e2e.json" + ], + "createDefaultProgram": true + }, + "plugins": [ + "rxjs", + "unicorn" + ], + "rules": { + "@typescript-eslint/no-floating-promises": "off" + } + } + ] +} diff --git a/e2e/playwright/authentication/exclude.tests.json b/e2e/playwright/authentication/exclude.tests.json new file mode 100644 index 0000000000..b8fb48b8af --- /dev/null +++ b/e2e/playwright/authentication/exclude.tests.json @@ -0,0 +1,3 @@ +{ + "C213097" : "https://alfresco.atlassian.net/browse/ACS-5479" +} diff --git a/e2e/playwright/authentication/playwright.config.ts b/e2e/playwright/authentication/playwright.config.ts new file mode 100644 index 0000000000..aa7afb6c92 --- /dev/null +++ b/e2e/playwright/authentication/playwright.config.ts @@ -0,0 +1,44 @@ +/*! + * Copyright © 2005-2023 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 . + */ + +import { PlaywrightTestConfig } from '@playwright/test'; +import { CustomConfig, getGlobalConfig, getExcludedTestsRegExpArray } from '@alfresco/playwright-shared'; +import EXCLUDED_JSON from './exclude.tests.json'; + +const config: PlaywrightTestConfig = { + ...getGlobalConfig, + + grepInvert: getExcludedTestsRegExpArray(EXCLUDED_JSON, 'Authentication'), + projects: [ + { + name: 'Authentication', + testDir: './src/tests', + use: { + users: ['hruser', 'admin'] + } + } + ] +}; + +export default config; diff --git a/e2e/playwright/authentication/project.json b/e2e/playwright/authentication/project.json new file mode 100644 index 0000000000..d906f84ddc --- /dev/null +++ b/e2e/playwright/authentication/project.json @@ -0,0 +1,34 @@ +{ + "$schema": "../../../node_modules/nx/schemas/project-schema.json", + "name": "authentication-e2e", + "sourceRoot": "e2e/playwright/authentication/src", + "projectType": "application", + "targets": { + "e2e": { + "executor": "nx:run-commands", + "options": { + "commands": [ + "npx playwright test --config=e2e/playwright/authentication/playwright.config.ts" + ] + }, + "configurations": { + "production": { + "devServerTarget": "content-ce:serve:production" + } + } + }, + "lint": { + "executor": "@angular-eslint/builder:lint", + "options": { + "lintFilePatterns": [ + "e2e/**/*.ts", + "e2e/**/*.html" + ], + "cache": true, + "cacheLocation": ".eslintcache", + "ignorePath": ".eslintignore" + }, + "outputs": ["{options.outputFile}"] + } + } +} diff --git a/e2e/playwright/authentication/src/tests/login.spec.ts b/e2e/playwright/authentication/src/tests/login.spec.ts new file mode 100644 index 0000000000..846a638bfc --- /dev/null +++ b/e2e/playwright/authentication/src/tests/login.spec.ts @@ -0,0 +1,105 @@ +/*! + * Copyright © 2005-2023 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 . + */ + +import { expect } from '@playwright/test'; +import { ApiClientFactory, Utils, test } from '@alfresco/playwright-shared'; + +test.describe('viewer file', () => { + const apiClientFactory = new ApiClientFactory(); + + const otherLanguageUser = { + /* cspell:disable-next-line */ + username: `пользвате${Utils.random()}`, + password: '密碼中國' + }; + + const johnDoe = { + username: `user-${Utils.random()}`, + get password() { + return this.username; + }, + firstName: 'John', + lastName: 'Doe' + }; + + const testUser2 = { + username: `user-${Utils.random()}`, + password: 'user2 password' + }; + const newPassword = 'new password'; + + test.beforeAll(async () => { + await apiClientFactory.setUpAcaBackend('admin'); + + await apiClientFactory.createUser(otherLanguageUser); + await apiClientFactory.createUser(johnDoe); + await apiClientFactory.createUser(testUser2); + }); + + test.describe('general tests', () => { + test('[C213089] login page layout', async ({ loginPage }) => { + await loginPage.navigate(); + expect(await loginPage.username.isEnabled(), 'username input is not enabled').toBe(true); + expect(await loginPage.password.isEnabled(), 'password input is not enabled').toBe(true); + expect(await loginPage.submitButton.isEnabled(), 'SIGN IN button is enabled').toBe(false); + await loginPage.password.fill('text'); + expect(await loginPage.isPasswordDisplayed(), 'Password is not hidden by default').toBe(false); + await loginPage.passwordVisibility.click(); + expect(await loginPage.isPasswordDisplayed(), 'Password is not visible on show password').toBe(true); + }); + }); + + test.describe('with invalid credentials', () => { + test('[C213106] unauthenticated user is redirected to Login page', async ({ personalFiles }) => { + await personalFiles.navigate(); + expect(personalFiles.page.url()).toContain('login'); + }); + }); + + test.describe('with valid credentials', () => { + test('[C213097] logs in with user with non-latin characters', async ({ loginPage }) => { + await loginPage.navigate(); + await loginPage.loginUser({ username: otherLanguageUser.username, password: otherLanguageUser.password }); + expect(await loginPage.page.url()).toContain('personal-files'); + }); + + test('[C213107] redirects to Home Page when navigating to the Login page while already logged in', async ({ loginPage }) => { + const { username } = johnDoe; + await loginPage.navigate(); + await loginPage.loginUser({ username: username, password: username }); + await loginPage.userProfileButton.waitFor({ state: 'attached' }); + await loginPage.navigate(); + await loginPage.userProfileButton.waitFor({ state: 'attached' }); + expect(loginPage.page.url()).toContain('personal-files'); + }); + + test('[C213104] user is able to login after changing his password', async ({ loginPage }) => { + await apiClientFactory.changePassword(testUser2.username, newPassword); + await loginPage.navigate(); + await loginPage.loginUser({ username: testUser2.username, password: newPassword }); + await loginPage.userProfileButton.waitFor({ state: 'attached' }); + expect(loginPage.page.url()).toContain('personal-files'); + }); + }); +}); diff --git a/e2e/playwright/authentication/src/tests/logout.spec.ts b/e2e/playwright/authentication/src/tests/logout.spec.ts new file mode 100644 index 0000000000..706f9cbb8c --- /dev/null +++ b/e2e/playwright/authentication/src/tests/logout.spec.ts @@ -0,0 +1,49 @@ +/*! + * Copyright © 2005-2023 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 . + */ + +import { expect } from '@playwright/test'; +import { ApiClientFactory, Utils, test } from '@alfresco/playwright-shared'; + +test.describe('viewer file', () => { + const apiClientFactory = new ApiClientFactory(); + const testUser = { + username: `user-${Utils.random()}`, + password: 'user password' + }; + test.beforeAll(async () => { + await apiClientFactory.setUpAcaBackend('admin'); + await apiClientFactory.createUser(testUser); + }); + + test('[C213145] redirects to Login page when pressing browser Back after logout', async ({ loginPage }) => { + await loginPage.navigate(); + await loginPage.loginUser({ username: testUser.username, password: testUser.password }); + await loginPage.logoutUser(); + await loginPage.username.waitFor({ state: 'attached' }); + expect(loginPage.page.url()).toContain('login'); + await loginPage.page.goBack(); + await loginPage.username.waitFor({ state: 'attached' }); + expect(loginPage.page.url()).toContain('login'); + }); +}); diff --git a/e2e/playwright/authentication/tsconfig.e2e.adf.json b/e2e/playwright/authentication/tsconfig.e2e.adf.json new file mode 100644 index 0000000000..87cbcf775a --- /dev/null +++ b/e2e/playwright/authentication/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/authentication/tsconfig.e2e.json b/e2e/playwright/authentication/tsconfig.e2e.json new file mode 100755 index 0000000000..d1d140fba4 --- /dev/null +++ b/e2e/playwright/authentication/tsconfig.e2e.json @@ -0,0 +1,12 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "outDir": "../../out-tsc/e2e", + "baseUrl": "./", + "module": "commonjs", + "target": "es2017", + "types": ["jasmine", "jasminewd2", "node", "@playwright/test"], + "skipLibCheck": true, + }, + "exclude": ["node_modules"] +} diff --git a/e2e/protractor/suites/authentication/login.test.ts b/e2e/protractor/suites/authentication/login.test.ts deleted file mode 100755 index cc64080d60..0000000000 --- a/e2e/protractor/suites/authentication/login.test.ts +++ /dev/null @@ -1,167 +0,0 @@ -/*! - * Copyright © 2005-2023 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 . - */ - -import { browser } from 'protractor'; -import { AdminActions, APP_ROUTES, LoginPage, Utils, navigate, BrowsingPage } from '@alfresco/aca-testing-shared'; -import { BrowserActions } from '@alfresco/adf-testing'; - -describe('Login', () => { - const loginPage = new LoginPage(); - const { login } = loginPage; - const adminApiActions = new AdminActions(); - - /* cspell:disable-next-line */ - const testUser = `user-${Utils.random()}@alfness`; - - const russianUser = { - /* cspell:disable-next-line */ - username: `пользвате${Utils.random()}`, - password: '密碼中國' - }; - - const johnDoe = { - username: `user-${Utils.random()}`, - get password() { - return this.username; - }, - firstName: 'John', - lastName: 'Doe' - }; - - const disabledUser = `user-${Utils.random()}`; - const testUser2 = { - username: `user-${Utils.random()}`, - password: 'user2 password' - }; - const newPassword = 'new password'; - - beforeAll(async () => { - await adminApiActions.createUser({ username: testUser }); - await adminApiActions.createUser(russianUser); - await adminApiActions.createUser(johnDoe); - await adminApiActions.createUser({ username: disabledUser }); - await adminApiActions.createUser(testUser2); - await adminApiActions.disableUser(disabledUser); - }); - - afterEach(async () => { - await Utils.clearLocalStorage(); - }); - - describe('general tests', () => { - beforeEach(async () => { - await loginPage.load(); - }); - - it('[C213089] login page layout', async () => { - expect(await login.usernameInput.isEnabled()).toBe(true, 'username input is not enabled'); - expect(await login.passwordInput.isEnabled()).toBe(true, 'password input is not enabled'); - expect(await login.submitButton.isEnabled()).toBe(false, 'SIGN IN button is enabled'); - expect(await login.isPasswordHidden()).toBe(true, 'Password is not hidden by default'); - }); - - it('[C213091] change password visibility', async () => { - await login.enterPassword('some password'); - expect(await login.isPasswordDisplayed()).toBe(false, 'password is visible'); - await BrowserActions.click(login.passwordVisibility); - - expect(await login.isPasswordHidden()).toBe(false, 'Password visibility not changed'); - expect(await login.isPasswordDisplayed()).toBe(true, 'password is not visible'); - }); - }); - - describe('with valid credentials', () => { - it('[C213092] navigate to "Personal Files"', async () => { - const { username } = johnDoe; - - await loginPage.loginWith(username); - expect(await browser.getCurrentUrl()).toContain(APP_ROUTES.PERSONAL_FILES); - }); - - it(`[C213096] logs in with user having username containing "@"`, async () => { - await loginPage.loginWith(testUser); - expect(await browser.getCurrentUrl()).toContain(APP_ROUTES.PERSONAL_FILES); - }); - - it('[C213097] logs in with user with non-latin characters', async () => { - const { username, password } = russianUser; - - await loginPage.loginWith(username, password); - expect(await browser.getCurrentUrl()).toContain(APP_ROUTES.PERSONAL_FILES); - }); - - it('[C213107] redirects to Home Page when navigating to the Login page while already logged in', async () => { - const { username } = johnDoe; - - await loginPage.loginWith(username); - - await navigate(APP_ROUTES.LOGIN); - expect(await browser.getCurrentUrl()).toContain(APP_ROUTES.PERSONAL_FILES); - }); - - it('[C213109] redirects to Personal Files when pressing browser Back while already logged in', async () => { - const { username } = johnDoe; - - await loginPage.loginWith(username); - await browser.navigate().back(); - expect(await browser.getCurrentUrl()).toContain(APP_ROUTES.PERSONAL_FILES); - }); - - it('[C213104] user is able to login after changing his password', async () => { - await loginPage.loginWith(testUser2.username, testUser2.password); - const page = new BrowsingPage(); - await page.signOut(); - - await adminApiActions.login(); - await adminApiActions.changePassword(testUser2.username, newPassword); - - await loginPage.loginWith(testUser2.username, newPassword); - expect(await browser.getCurrentUrl()).toContain(APP_ROUTES.PERSONAL_FILES); - }); - }); - - describe('with invalid credentials', () => { - const { login: loginComponent } = loginPage; - const { submitButton } = loginComponent; - - beforeEach(async () => { - await loginPage.load(); - }); - - it('[C280072] disabled submit button when password is empty', async () => { - await loginComponent.enterUsername('any-username'); - expect(await submitButton.isEnabled()).toBe(false, 'submit button is enabled'); - }); - - it('[C280070] disabled submit button when username is empty', async () => { - await loginPage.login.enterPassword('any-password'); - expect(await submitButton.isEnabled()).toBe(false, 'submit button is enabled'); - }); - - it('[C213106] unauthenticated user is redirected to Login page', async () => { - await navigate(APP_ROUTES.PERSONAL_FILES); - expect(await browser.getCurrentUrl()).toContain(APP_ROUTES.LOGIN); - }); - }); -}); diff --git a/e2e/protractor/suites/authentication/logout.test.ts b/e2e/protractor/suites/authentication/logout.test.ts deleted file mode 100755 index bc08841d59..0000000000 --- a/e2e/protractor/suites/authentication/logout.test.ts +++ /dev/null @@ -1,63 +0,0 @@ -/*! - * Copyright © 2005-2023 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 . - */ - -import { browser } from 'protractor'; -import { AdminActions, LoginPage, BrowsingPage, Utils, APP_ROUTES } from '@alfresco/aca-testing-shared'; - -describe('Logout', () => { - const page = new BrowsingPage(); - const loginPage = new LoginPage(); - const johnDoe = `user-${Utils.random()}`; - const adminApiActions = new AdminActions(); - - beforeAll(async () => { - await adminApiActions.createUser({ username: johnDoe }); - }); - - beforeEach(async () => { - await loginPage.loginWith(johnDoe); - }); - - it('[C213143] Sign out option is available', async () => { - await page.header.openMoreMenu(); - expect(await page.header.menu.isMenuItemPresent('Sign out')).toBe(true, 'Sign out option not displayed'); - }); - - it('[C213144] redirects to Login page on sign out', async () => { - await page.signOut(); - expect(await browser.getCurrentUrl()).toContain(APP_ROUTES.LOGIN); - }); - - it('[C213145] redirects to Login page when pressing browser Back after logout', async () => { - await page.signOut(); - await browser.navigate().back(); - expect(await browser.getCurrentUrl()).toContain(APP_ROUTES.LOGIN); - }); - - it('[C213146] redirects to Login page when trying to access a part of the app after logout', async () => { - await page.signOut(); - await page.load('/favorites'); - expect(await browser.getCurrentUrl()).toContain(APP_ROUTES.LOGIN); - }); -}); diff --git a/projects/aca-playwright-shared/src/api/api-client-factory.ts b/projects/aca-playwright-shared/src/api/api-client-factory.ts index f35f2af3cd..13b55375ff 100644 --- a/projects/aca-playwright-shared/src/api/api-client-factory.ts +++ b/projects/aca-playwright-shared/src/api/api-client-factory.ts @@ -163,4 +163,15 @@ export class ApiClientFactory { return null; } } + + async changePassword(username: string, newPassword: string): Promise { + const peopleApi = new PeopleApi(this.alfrescoApi); + + try { + return peopleApi.updatePerson(username, { password: newPassword }); + } catch (error) { + logger.error('[API Client Factory] changePassword failed : ', error); + return null; + } + } } diff --git a/projects/aca-playwright-shared/src/fixtures/page-initialization.ts b/projects/aca-playwright-shared/src/fixtures/page-initialization.ts index de1107d282..4615c8365a 100644 --- a/projects/aca-playwright-shared/src/fixtures/page-initialization.ts +++ b/projects/aca-playwright-shared/src/fixtures/page-initialization.ts @@ -35,7 +35,8 @@ import { FavoritesPage, FavoritesPageApi, TrashPage, - UserActions + UserActions, + LoginPage } from '../'; interface Pages { @@ -47,6 +48,7 @@ interface Pages { searchPage: SearchPage; favoritePage: FavoritesPage; trashPage: TrashPage; + loginPage: LoginPage; } interface Api { @@ -78,6 +80,9 @@ export const test = base.extend({ trashPage: async ({ page }, use) => { await use(new TrashPage(page)); }, + loginPage: async ({ page }, use) => { + await use(new LoginPage(page)); + }, // eslint-disable-next-line no-empty-pattern fileAction: async ({}, use) => { await use(await FileActionsApi.initialize('hruser')); diff --git a/projects/aca-playwright-shared/src/page-objects/pages/login.page.ts b/projects/aca-playwright-shared/src/page-objects/pages/login.page.ts index f8ca74b8f8..da2c68127f 100644 --- a/projects/aca-playwright-shared/src/page-objects/pages/login.page.ts +++ b/projects/aca-playwright-shared/src/page-objects/pages/login.page.ts @@ -31,15 +31,18 @@ interface LoginOptions { withNavigation?: boolean; } export class LoginPage extends BasePage { + private static pageUrl = 'login'; + constructor(page: Page) { - super(page, ''); + super(page, LoginPage.pageUrl); } - private username = this.page.locator('#username'); - private password = this.page.locator('#password'); - private submitButton = this.page.locator('#login-button'); - private userProfileButton = this.page.locator('aca-user-menu button'); - private userLogOutButton = this.page.locator('aca-logout button'); + public username = this.page.locator('#username'); + public password = this.page.locator('#password'); + public submitButton = this.page.locator('#login-button'); + public userProfileButton = this.page.locator('aca-user-menu button'); + public userLogOutButton = this.page.locator('aca-logout button'); + public passwordVisibility = this.page.locator('.adf-login-password-icon'); async loginUser(userData: { username: string; password: string } | UserModel, options?: LoginOptions): Promise { if (options?.withNavigation) { @@ -58,4 +61,12 @@ export class LoginPage extends BasePage { await this.userProfileButton.click(); await this.userLogOutButton.click(); } + + async isPasswordDisplayed(): Promise { + const type = await this.password.getAttribute('type'); + if (type === 'text') { + return true; + } + return false; + } }