From 6ee6beaaf67f6af1add25039695a2f4acf892675 Mon Sep 17 00:00:00 2001 From: swadeley Date: Fri, 25 Apr 2025 13:58:20 +0100 Subject: [PATCH 01/24] Add two-user RBAC test --- _playwright-tests/UI/TwoUserRBAC.spec.ts | 96 ++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 _playwright-tests/UI/TwoUserRBAC.spec.ts diff --git a/_playwright-tests/UI/TwoUserRBAC.spec.ts b/_playwright-tests/UI/TwoUserRBAC.spec.ts new file mode 100644 index 000000000..2c4c40750 --- /dev/null +++ b/_playwright-tests/UI/TwoUserRBAC.spec.ts @@ -0,0 +1,96 @@ +import { test, expect } from '@playwright/test'; +import { navigateToRepositories } from './helpers/navHelpers'; +import { deleteAllRepos } from './helpers/deleteRepositories'; +import { closePopupsIfExist, getRowByNameOrUrl } from './helpers/helpers'; +import { logInWithUsernameAndPassword } from "../helpers/loginHelpers"; + +export const url = `https://stephenw.fedorapeople.org/centirepos/repo99/`; + +export const repoNamePrefix = 'Repo-RBAC'; +export const randomName = () => (Math.random() + 1).toString(36).substring(2, 6); +export const repoName = `${repoNamePrefix}-${randomName()}`; + +test.describe('User Permissions Test', () => { + test.beforeAll(async ({ browser }) => { + // Default user login + if (!process.env.USER1USERNAME || !process.env.STAGE_RO_USER_USERNAME) { + throw new Error('Required environment variables are not set'); + }; + let context = await browser.newContext(); + let page = await context.newPage(); + await logInWithUsernameAndPassword(page, process.env.USER1USERNAME!); + await context.storageState({ path: 'default_user.json' }); + await context.close(); + + // Read-only user login + context = await browser.newContext(); + page = await context.newPage(); + await logInWithUsernameAndPassword(page, process.env.STAGE_RO_USER_USERNAME!); + await context.storageState({ path: 'readonly_user.json' }); + await context.close(); + }); + + test('Default user configures repo', async ({ browser }) => { + const context = await browser.newContext({ storageState: 'default_user.json' }); + const page = await context.newPage(); + await deleteAllRepos(page, `&search=${repoNamePrefix}`); + await navigateToRepositories(page); + await closePopupsIfExist(page); + + await test.step('Create a repository', async () => { + // Click on the 'Add repositories' button + // HMS-5268 There are two buttons on the ZeroState page + await page.pause(); + await page.getByRole('button', { name: 'Add repositories' }).first().click(); + await expect(page.getByRole('dialog', { name: 'Add custom repositories' })).toBeVisible(); + + // Fill in the repository details + await page.getByLabel('Name').fill(repoName); + await page.getByLabel('Introspect only').click(); + await page.getByLabel('URL').fill(url); + await page.getByRole('button', { name: 'Save', exact: true }).click(); + }); + await test.step('Read the repo', async () => { + // Search for the created repo + await page.getByRole('textbox', { name: 'Filter by name/url' }).fill(repoName); + const row = await getRowByNameOrUrl(page, repoName); + await expect(row.getByText('Valid')).toBeVisible(); + await row.getByLabel('Kebab toggle').click(); + // Click on the Edit button to see the repo + await row.getByRole('menuitem', { name: 'Edit' }).click(); + await expect(page.getByRole('dialog', { name: 'Edit custom repository' })).toBeVisible(); + // Assert we can read some values + await expect(page.getByPlaceholder('Enter name', { exact: true })).toHaveValue(repoName); + await expect(page.getByPlaceholder('https://', { exact: true })).toHaveValue(url); + }); + await test.step('Update the repository', async () => { + await page.getByPlaceholder('Enter name', { exact: true }).fill(`${repoName}-Edited`); + await page.pause(); + await page.getByRole('button', { name: 'Save changes', exact: true }).click(); + }); + + await context.close(); + }); + + test('Read-only user can view but not edit', async ({ browser }) => { + const context = await browser.newContext({ storageState: 'readonly_user.json' }); + const page = await context.newPage(); + await navigateToRepositories(page); + await closePopupsIfExist(page); + + // Assert can read + // Search for the created repo + await page.getByRole('textbox', { name: 'Filter by name/url' }).fill(repoName); + const row = await getRowByNameOrUrl(page, repoName); + await expect(row.getByText('Valid')).toBeVisible(); + await row.getByLabel('Kebab toggle').click(); + // Assert we cannot click on the Edit button to see the repo + // You do not have the required permissions to perform this action. + await row.getByRole('menuitem', { name: 'Edit' }).click(); + await expect(page.getByText('You do not have the required permissions to perform this action')).toBeVisible(); + await expect(page.getByRole('dialog', { name: 'Edit custom repository' })).not.toBeVisible(); + + await context.close(); + }); + +}); \ No newline at end of file From 9923bf1ed77dba7a79103912c2f4766271904abc Mon Sep 17 00:00:00 2001 From: swadeley Date: Fri, 25 Apr 2025 14:57:24 +0100 Subject: [PATCH 02/24] Add read-only user to setup --- _playwright-tests/auth.setup.ts | 38 +++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/_playwright-tests/auth.setup.ts b/_playwright-tests/auth.setup.ts index a35a103bb..af86ff5af 100644 --- a/_playwright-tests/auth.setup.ts +++ b/_playwright-tests/auth.setup.ts @@ -1,9 +1,12 @@ -import { expect, test as setup } from '@playwright/test'; +import { expect, test as setup, type Page } from "@playwright/test"; import { - throwIfMissingEnvVariables, - logInWithUser1, - storeStorageStateAndToken, -} from './helpers/loginHelpers'; + closePopupsIfExist, +} from './UI/helpers/helpers'; +import { + throwIfMissingEnvVariables, + logInWithUsernameAndPassword, + logout, +} from "./helpers/loginHelpers"; import { closePopupsIfExist } from './UI/helpers/helpers'; setup.describe('Setup', async () => { @@ -17,7 +20,30 @@ setup.describe('Setup', async () => { setup.setTimeout(60_000); await closePopupsIfExist(page); - await logInWithUser1(page); + await logInWithUsernameAndPassword( + page, + process.env.USER1USERNAME, + process.env.USER1PASSWORD, + ); + await storeStorageStateAndToken(page); + await logout(page); + await logInWithUsernameAndPassword( + page, + process.env.RO_USER_USERNAME, + process.env.RO_USER_PASSWORD, + ); await storeStorageStateAndToken(page); + await logout(page); + // Example of how to add another user + // await logout(page) + // await logInWithUsernameAndPassword( + // page, + // process.env.USER2USERNAME, + // process.env.USER2PASSWORD + // ); + // Example of how to switch to said user + // await switchToUser(page, process.env.USER1USERNAME!); + // await ensureNotInPreview(page); + // Other users for other tests can be added below after logging out }); }); From 0cbca4f0626493b2f4ffe0d1b9aae844c1d2cd6e Mon Sep 17 00:00:00 2001 From: swadeley Date: Thu, 1 May 2025 18:59:06 +0100 Subject: [PATCH 03/24] fixup auth setup --- _playwright-tests/auth.setup.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_playwright-tests/auth.setup.ts b/_playwright-tests/auth.setup.ts index af86ff5af..dae930e71 100644 --- a/_playwright-tests/auth.setup.ts +++ b/_playwright-tests/auth.setup.ts @@ -5,9 +5,9 @@ import { import { throwIfMissingEnvVariables, logInWithUsernameAndPassword, + storeStorageStateAndToken, logout, } from "./helpers/loginHelpers"; -import { closePopupsIfExist } from './UI/helpers/helpers'; setup.describe('Setup', async () => { setup.describe.configure({ retries: 3 }); From 97924ad71b50c7bdfa9989b9dd9ce572b7aff92b Mon Sep 17 00:00:00 2001 From: swadeley Date: Thu, 1 May 2025 19:05:29 +0100 Subject: [PATCH 04/24] update to new RO_USER_USERNAME --- _playwright-tests/UI/TwoUserRBAC.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/_playwright-tests/UI/TwoUserRBAC.spec.ts b/_playwright-tests/UI/TwoUserRBAC.spec.ts index 2c4c40750..5174ff9bb 100644 --- a/_playwright-tests/UI/TwoUserRBAC.spec.ts +++ b/_playwright-tests/UI/TwoUserRBAC.spec.ts @@ -13,7 +13,7 @@ export const repoName = `${repoNamePrefix}-${randomName()}`; test.describe('User Permissions Test', () => { test.beforeAll(async ({ browser }) => { // Default user login - if (!process.env.USER1USERNAME || !process.env.STAGE_RO_USER_USERNAME) { + if (!process.env.USER1USERNAME || !process.env.RO_USER_USERNAME) { throw new Error('Required environment variables are not set'); }; let context = await browser.newContext(); @@ -25,7 +25,7 @@ test.describe('User Permissions Test', () => { // Read-only user login context = await browser.newContext(); page = await context.newPage(); - await logInWithUsernameAndPassword(page, process.env.STAGE_RO_USER_USERNAME!); + await logInWithUsernameAndPassword(page, process.env.RO_USER_USERNAME!); await context.storageState({ path: 'readonly_user.json' }); await context.close(); }); From a9323d6229c086377b61fd65d97101a84784359f Mon Sep 17 00:00:00 2001 From: swadeley Date: Wed, 14 May 2025 13:23:02 +0800 Subject: [PATCH 05/24] Move code out of "beforeAll" and clear cookies --- _playwright-tests/UI/TwoUserRBAC.spec.ts | 39 ++++++++---------------- 1 file changed, 12 insertions(+), 27 deletions(-) diff --git a/_playwright-tests/UI/TwoUserRBAC.spec.ts b/_playwright-tests/UI/TwoUserRBAC.spec.ts index 5174ff9bb..428a73e3d 100644 --- a/_playwright-tests/UI/TwoUserRBAC.spec.ts +++ b/_playwright-tests/UI/TwoUserRBAC.spec.ts @@ -2,7 +2,7 @@ import { test, expect } from '@playwright/test'; import { navigateToRepositories } from './helpers/navHelpers'; import { deleteAllRepos } from './helpers/deleteRepositories'; import { closePopupsIfExist, getRowByNameOrUrl } from './helpers/helpers'; -import { logInWithUsernameAndPassword } from "../helpers/loginHelpers"; +import { logInWithUsernameAndPassword, logout, logInWithUser1 } from "../helpers/loginHelpers"; export const url = `https://stephenw.fedorapeople.org/centirepos/repo99/`; @@ -11,27 +11,8 @@ export const randomName = () => (Math.random() + 1).toString(36).substring(2, 6) export const repoName = `${repoNamePrefix}-${randomName()}`; test.describe('User Permissions Test', () => { - test.beforeAll(async ({ browser }) => { - // Default user login - if (!process.env.USER1USERNAME || !process.env.RO_USER_USERNAME) { - throw new Error('Required environment variables are not set'); - }; - let context = await browser.newContext(); - let page = await context.newPage(); - await logInWithUsernameAndPassword(page, process.env.USER1USERNAME!); - await context.storageState({ path: 'default_user.json' }); - await context.close(); - - // Read-only user login - context = await browser.newContext(); - page = await context.newPage(); - await logInWithUsernameAndPassword(page, process.env.RO_USER_USERNAME!); - await context.storageState({ path: 'readonly_user.json' }); - await context.close(); - }); - test('Default user configures repo', async ({ browser }) => { - const context = await browser.newContext({ storageState: 'default_user.json' }); + const context = await browser.newContext({ storageState: '.auth/default_user.json' }); const page = await context.newPage(); await deleteAllRepos(page, `&search=${repoNamePrefix}`); await navigateToRepositories(page); @@ -40,7 +21,6 @@ test.describe('User Permissions Test', () => { await test.step('Create a repository', async () => { // Click on the 'Add repositories' button // HMS-5268 There are two buttons on the ZeroState page - await page.pause(); await page.getByRole('button', { name: 'Add repositories' }).first().click(); await expect(page.getByRole('dialog', { name: 'Add custom repositories' })).toBeVisible(); @@ -65,20 +45,24 @@ test.describe('User Permissions Test', () => { }); await test.step('Update the repository', async () => { await page.getByPlaceholder('Enter name', { exact: true }).fill(`${repoName}-Edited`); - await page.pause(); await page.getByRole('button', { name: 'Save changes', exact: true }).click(); }); - - await context.close(); + await page.context().clearCookies(); + await logout(page); + await page.context().close(); }); test('Read-only user can view but not edit', async ({ browser }) => { - const context = await browser.newContext({ storageState: 'readonly_user.json' }); + const context = await browser.newContext({ storageState: { cookies: [], origins: [] } }); const page = await context.newPage(); + // Read-only user login + await page.context().clearCookies(); + await logInWithUsernameAndPassword(page, process.env.RO_USER_USERNAME!, process.env.RO_USER_PASSWORD!); await navigateToRepositories(page); await closePopupsIfExist(page); - // Assert can read + // Assert read-only user can list but not edit previously created repo + await expect(page.getByRole('button', { name: 'Add repositories', exact: true })).not.toBeEnabled(); // Search for the created repo await page.getByRole('textbox', { name: 'Filter by name/url' }).fill(repoName); const row = await getRowByNameOrUrl(page, repoName); @@ -93,4 +77,5 @@ test.describe('User Permissions Test', () => { await context.close(); }); + }); }); \ No newline at end of file From a49e66188b89f55d27c4d59d27e83c9afd7b27d0 Mon Sep 17 00:00:00 2001 From: swadeley Date: Wed, 14 May 2025 13:24:14 +0800 Subject: [PATCH 06/24] remove storeStorageStateAndToken call --- _playwright-tests/auth.setup.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/_playwright-tests/auth.setup.ts b/_playwright-tests/auth.setup.ts index dae930e71..b907f518e 100644 --- a/_playwright-tests/auth.setup.ts +++ b/_playwright-tests/auth.setup.ts @@ -32,7 +32,6 @@ setup.describe('Setup', async () => { process.env.RO_USER_USERNAME, process.env.RO_USER_PASSWORD, ); - await storeStorageStateAndToken(page); await logout(page); // Example of how to add another user // await logout(page) From 5bb0896acb83a74fef51297b921a4fa9be8f8ea6 Mon Sep 17 00:00:00 2001 From: swadeley Date: Wed, 14 May 2025 13:26:10 +0800 Subject: [PATCH 07/24] user > default_user --- _playwright-tests/helpers/loginHelpers.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_playwright-tests/helpers/loginHelpers.tsx b/_playwright-tests/helpers/loginHelpers.tsx index a58059591..536cf5134 100644 --- a/_playwright-tests/helpers/loginHelpers.tsx +++ b/_playwright-tests/helpers/loginHelpers.tsx @@ -58,7 +58,7 @@ export const logInWithUser1 = async (page: Page) => export const storeStorageStateAndToken = async (page: Page) => { const { cookies } = await page .context() - .storageState({ path: path.join(__dirname, '../../.auth/user.json') }); + .storageState({ path: path.join(__dirname, '../../.auth/default_user.json') }); process.env.TOKEN = `Bearer ${cookies.find((cookie) => cookie.name === 'cs_jwt')?.value}`; await page.waitForTimeout(100); }; From 7f8a1a7dbc0f10e7f077e296a00c16f3961a09d8 Mon Sep 17 00:00:00 2001 From: swadeley Date: Wed, 14 May 2025 13:26:36 +0800 Subject: [PATCH 08/24] user > default_user --- playwright.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playwright.config.ts b/playwright.config.ts index e00f782fb..7b2772a68 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -56,7 +56,7 @@ export default defineConfig({ name: 'chromium', use: { ...devices['Desktop Chrome'], - storageState: '.auth/user.json', + storageState: '.auth/default_user.json', }, dependencies: ['setup'], }, From 7c392fa2865dfb5bb3371699fa7ff705c6b31989 Mon Sep 17 00:00:00 2001 From: swadeley Date: Wed, 14 May 2025 13:28:20 +0800 Subject: [PATCH 09/24] pf6 support --- _playwright-tests/helpers/loginHelpers.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_playwright-tests/helpers/loginHelpers.tsx b/_playwright-tests/helpers/loginHelpers.tsx index 536cf5134..74d0399c0 100644 --- a/_playwright-tests/helpers/loginHelpers.tsx +++ b/_playwright-tests/helpers/loginHelpers.tsx @@ -5,7 +5,7 @@ import path from 'path'; export const logout = async (page: Page) => { const button = await page.locator( - 'div.pf-v5-c-toolbar__item.pf-m-hidden.pf-m-visible-on-lg.pf-v5-u-mr-0 > button', + 'div.pf-v6-c-toolbar__item.pf-m-hidden.pf-m-visible-on-lg.pf-v6-u-mr-0 > button', ); await button.click(); From b234fabcac710911db5f408ea5b37547333ef4c1 Mon Sep 17 00:00:00 2001 From: swadeley Date: Wed, 14 May 2025 13:35:45 +0800 Subject: [PATCH 10/24] fix test end --- _playwright-tests/UI/TwoUserRBAC.spec.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/_playwright-tests/UI/TwoUserRBAC.spec.ts b/_playwright-tests/UI/TwoUserRBAC.spec.ts index 428a73e3d..c6f920f1e 100644 --- a/_playwright-tests/UI/TwoUserRBAC.spec.ts +++ b/_playwright-tests/UI/TwoUserRBAC.spec.ts @@ -76,6 +76,4 @@ test.describe('User Permissions Test', () => { await context.close(); }); - - }); -}); \ No newline at end of file + }); \ No newline at end of file From 999af4a203963f74f6a98bdc2ffd70880dab294f Mon Sep 17 00:00:00 2001 From: swadeley Date: Wed, 14 May 2025 20:48:51 +0800 Subject: [PATCH 11/24] Load storageState: readonly_user --- _playwright-tests/UI/TwoUserRBAC.spec.ts | 26 +++++++++--------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/_playwright-tests/UI/TwoUserRBAC.spec.ts b/_playwright-tests/UI/TwoUserRBAC.spec.ts index c6f920f1e..495c217d2 100644 --- a/_playwright-tests/UI/TwoUserRBAC.spec.ts +++ b/_playwright-tests/UI/TwoUserRBAC.spec.ts @@ -3,8 +3,9 @@ import { navigateToRepositories } from './helpers/navHelpers'; import { deleteAllRepos } from './helpers/deleteRepositories'; import { closePopupsIfExist, getRowByNameOrUrl } from './helpers/helpers'; import { logInWithUsernameAndPassword, logout, logInWithUser1 } from "../helpers/loginHelpers"; +import { randomUrl } from './helpers/repoHelpers'; -export const url = `https://stephenw.fedorapeople.org/centirepos/repo99/`; +export const url = randomUrl(); export const repoNamePrefix = 'Repo-RBAC'; export const randomName = () => (Math.random() + 1).toString(36).substring(2, 6); @@ -14,7 +15,7 @@ test.describe('User Permissions Test', () => { test('Default user configures repo', async ({ browser }) => { const context = await browser.newContext({ storageState: '.auth/default_user.json' }); const page = await context.newPage(); - await deleteAllRepos(page, `&search=${repoNamePrefix}`); + // await deleteAllRepos(page, `&search=${repoNamePrefix}`); await navigateToRepositories(page); await closePopupsIfExist(page); @@ -53,27 +54,20 @@ test.describe('User Permissions Test', () => { }); test('Read-only user can view but not edit', async ({ browser }) => { - const context = await browser.newContext({ storageState: { cookies: [], origins: [] } }); + const context = await browser.newContext({ storageState: '.auth/readonly_user.json' }); const page = await context.newPage(); - // Read-only user login - await page.context().clearCookies(); - await logInWithUsernameAndPassword(page, process.env.RO_USER_USERNAME!, process.env.RO_USER_PASSWORD!); await navigateToRepositories(page); await closePopupsIfExist(page); // Assert read-only user can list but not edit previously created repo - await expect(page.getByRole('button', { name: 'Add repositories', exact: true })).not.toBeEnabled(); - // Search for the created repo - await page.getByRole('textbox', { name: 'Filter by name/url' }).fill(repoName); const row = await getRowByNameOrUrl(page, repoName); await expect(row.getByText('Valid')).toBeVisible(); await row.getByLabel('Kebab toggle').click(); // Assert we cannot click on the Edit button to see the repo - // You do not have the required permissions to perform this action. - await row.getByRole('menuitem', { name: 'Edit' }).click(); - await expect(page.getByText('You do not have the required permissions to perform this action')).toBeVisible(); - await expect(page.getByRole('dialog', { name: 'Edit custom repository' })).not.toBeVisible(); - - await context.close(); - }); + await expect(row.locator(".pf-v5-c-menu__list")).toBeVisible({ timeout: 5000 }); // Confirm menu is open + await expect(row.getByRole("menuitem", { name: "Edit" })).not.toBeVisible({ timeout: 1000 }); + // You do not have the required permissions to perform this action. + const repoButton = page.getByRole('button', { name: 'Add repositories', exact: true }); + await expect(repoButton).toBeDisabled(); // Verify button is disabled + }); }); \ No newline at end of file From 307ad53866ee98c35a77428de11877331ea54a6a Mon Sep 17 00:00:00 2001 From: swadeley Date: Wed, 14 May 2025 20:49:38 +0800 Subject: [PATCH 12/24] Save read-only user storage state --- _playwright-tests/auth.setup.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/_playwright-tests/auth.setup.ts b/_playwright-tests/auth.setup.ts index b907f518e..f7fba389f 100644 --- a/_playwright-tests/auth.setup.ts +++ b/_playwright-tests/auth.setup.ts @@ -25,13 +25,16 @@ setup.describe('Setup', async () => { process.env.USER1USERNAME, process.env.USER1PASSWORD, ); - await storeStorageStateAndToken(page); + // Save admin user storage state + await page.context().storageState({ path: '.auth/default_user.json' }); await logout(page); await logInWithUsernameAndPassword( page, process.env.RO_USER_USERNAME, process.env.RO_USER_PASSWORD, ); + // Save read-only user storage state + await page.context().storageState({ path: '.auth/readonly_user.json' }); await logout(page); // Example of how to add another user // await logout(page) From c6876546c0f94d09b74b37f5549376bf4f53fa97 Mon Sep 17 00:00:00 2001 From: swadeley Date: Thu, 15 May 2025 14:48:12 +0800 Subject: [PATCH 13/24] auth.setup lint fix --- _playwright-tests/auth.setup.ts | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/_playwright-tests/auth.setup.ts b/_playwright-tests/auth.setup.ts index f7fba389f..3facb270b 100644 --- a/_playwright-tests/auth.setup.ts +++ b/_playwright-tests/auth.setup.ts @@ -1,13 +1,10 @@ -import { expect, test as setup, type Page } from "@playwright/test"; +import { expect, test as setup } from '@playwright/test'; +import { closePopupsIfExist } from './UI/helpers/helpers'; import { - closePopupsIfExist, -} from './UI/helpers/helpers'; -import { - throwIfMissingEnvVariables, - logInWithUsernameAndPassword, - storeStorageStateAndToken, - logout, -} from "./helpers/loginHelpers"; + throwIfMissingEnvVariables, + logInWithUsernameAndPassword, + logout, +} from './helpers/loginHelpers'; setup.describe('Setup', async () => { setup.describe.configure({ retries: 3 }); @@ -20,11 +17,7 @@ setup.describe('Setup', async () => { setup.setTimeout(60_000); await closePopupsIfExist(page); - await logInWithUsernameAndPassword( - page, - process.env.USER1USERNAME, - process.env.USER1PASSWORD, - ); + await logInWithUsernameAndPassword(page, process.env.USER1USERNAME, process.env.USER1PASSWORD); // Save admin user storage state await page.context().storageState({ path: '.auth/default_user.json' }); await logout(page); From c21ba8e4a3827d82fe3d048c7511b8685cb06701 Mon Sep 17 00:00:00 2001 From: swadeley Date: Thu, 15 May 2025 14:48:48 +0800 Subject: [PATCH 14/24] RBAC test lint fix --- _playwright-tests/UI/TwoUserRBAC.spec.ts | 71 ++++++++++++------------ 1 file changed, 35 insertions(+), 36 deletions(-) diff --git a/_playwright-tests/UI/TwoUserRBAC.spec.ts b/_playwright-tests/UI/TwoUserRBAC.spec.ts index 495c217d2..01b001f05 100644 --- a/_playwright-tests/UI/TwoUserRBAC.spec.ts +++ b/_playwright-tests/UI/TwoUserRBAC.spec.ts @@ -1,8 +1,7 @@ import { test, expect } from '@playwright/test'; import { navigateToRepositories } from './helpers/navHelpers'; -import { deleteAllRepos } from './helpers/deleteRepositories'; import { closePopupsIfExist, getRowByNameOrUrl } from './helpers/helpers'; -import { logInWithUsernameAndPassword, logout, logInWithUser1 } from "../helpers/loginHelpers"; +import { logout } from '../helpers/loginHelpers'; import { randomUrl } from './helpers/repoHelpers'; export const url = randomUrl(); @@ -20,38 +19,38 @@ test.describe('User Permissions Test', () => { await closePopupsIfExist(page); await test.step('Create a repository', async () => { - // Click on the 'Add repositories' button - // HMS-5268 There are two buttons on the ZeroState page - await page.getByRole('button', { name: 'Add repositories' }).first().click(); - await expect(page.getByRole('dialog', { name: 'Add custom repositories' })).toBeVisible(); - - // Fill in the repository details - await page.getByLabel('Name').fill(repoName); - await page.getByLabel('Introspect only').click(); - await page.getByLabel('URL').fill(url); - await page.getByRole('button', { name: 'Save', exact: true }).click(); - }); - await test.step('Read the repo', async () => { - // Search for the created repo - await page.getByRole('textbox', { name: 'Filter by name/url' }).fill(repoName); - const row = await getRowByNameOrUrl(page, repoName); - await expect(row.getByText('Valid')).toBeVisible(); - await row.getByLabel('Kebab toggle').click(); - // Click on the Edit button to see the repo - await row.getByRole('menuitem', { name: 'Edit' }).click(); - await expect(page.getByRole('dialog', { name: 'Edit custom repository' })).toBeVisible(); - // Assert we can read some values - await expect(page.getByPlaceholder('Enter name', { exact: true })).toHaveValue(repoName); - await expect(page.getByPlaceholder('https://', { exact: true })).toHaveValue(url); - }); - await test.step('Update the repository', async () => { - await page.getByPlaceholder('Enter name', { exact: true }).fill(`${repoName}-Edited`); - await page.getByRole('button', { name: 'Save changes', exact: true }).click(); - }); - await page.context().clearCookies(); - await logout(page); - await page.context().close(); + // Click on the 'Add repositories' button + // HMS-5268 There are two buttons on the ZeroState page + await page.getByRole('button', { name: 'Add repositories' }).first().click(); + await expect(page.getByRole('dialog', { name: 'Add custom repositories' })).toBeVisible(); + + // Fill in the repository details + await page.getByLabel('Name').fill(repoName); + await page.getByLabel('Introspect only').click(); + await page.getByLabel('URL').fill(url); + await page.getByRole('button', { name: 'Save', exact: true }).click(); + }); + await test.step('Read the repo', async () => { + // Search for the created repo + await page.getByRole('textbox', { name: 'Filter by name/url' }).fill(repoName); + const row = await getRowByNameOrUrl(page, repoName); + await expect(row.getByText('Valid')).toBeVisible(); + await row.getByLabel('Kebab toggle').click(); + // Click on the Edit button to see the repo + await row.getByRole('menuitem', { name: 'Edit' }).click(); + await expect(page.getByRole('dialog', { name: 'Edit custom repository' })).toBeVisible(); + // Assert we can read some values + await expect(page.getByPlaceholder('Enter name', { exact: true })).toHaveValue(repoName); + await expect(page.getByPlaceholder('https://', { exact: true })).toHaveValue(url); }); + await test.step('Update the repository', async () => { + await page.getByPlaceholder('Enter name', { exact: true }).fill(`${repoName}-Edited`); + await page.getByRole('button', { name: 'Save changes', exact: true }).click(); + }); + await page.context().clearCookies(); + await logout(page); + await page.context().close(); + }); test('Read-only user can view but not edit', async ({ browser }) => { const context = await browser.newContext({ storageState: '.auth/readonly_user.json' }); @@ -64,10 +63,10 @@ test.describe('User Permissions Test', () => { await expect(row.getByText('Valid')).toBeVisible(); await row.getByLabel('Kebab toggle').click(); // Assert we cannot click on the Edit button to see the repo - await expect(row.locator(".pf-v5-c-menu__list")).toBeVisible({ timeout: 5000 }); // Confirm menu is open - await expect(row.getByRole("menuitem", { name: "Edit" })).not.toBeVisible({ timeout: 1000 }); + await expect(row.locator('.pf-v5-c-menu__list')).toBeVisible({ timeout: 5000 }); // Confirm menu is open + await expect(row.getByRole('menuitem', { name: 'Edit' })).not.toBeVisible({ timeout: 1000 }); // You do not have the required permissions to perform this action. const repoButton = page.getByRole('button', { name: 'Add repositories', exact: true }); await expect(repoButton).toBeDisabled(); // Verify button is disabled }); - }); \ No newline at end of file +}); From 161af892b590ceab1582d71c9c08746dd167c945 Mon Sep 17 00:00:00 2001 From: swadeley Date: Thu, 15 May 2025 14:50:41 +0800 Subject: [PATCH 15/24] RBAC test lint fix --- _playwright-tests/UI/TwoUserRBAC.spec.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/_playwright-tests/UI/TwoUserRBAC.spec.ts b/_playwright-tests/UI/TwoUserRBAC.spec.ts index 01b001f05..f3df04f94 100644 --- a/_playwright-tests/UI/TwoUserRBAC.spec.ts +++ b/_playwright-tests/UI/TwoUserRBAC.spec.ts @@ -1,7 +1,6 @@ import { test, expect } from '@playwright/test'; import { navigateToRepositories } from './helpers/navHelpers'; import { closePopupsIfExist, getRowByNameOrUrl } from './helpers/helpers'; -import { logout } from '../helpers/loginHelpers'; import { randomUrl } from './helpers/repoHelpers'; export const url = randomUrl(); @@ -48,7 +47,6 @@ test.describe('User Permissions Test', () => { await page.getByRole('button', { name: 'Save changes', exact: true }).click(); }); await page.context().clearCookies(); - await logout(page); await page.context().close(); }); From 077e0f17b49fa6310ca5d278c77d08ece5df8d7b Mon Sep 17 00:00:00 2001 From: swadeley Date: Thu, 15 May 2025 20:02:47 +0800 Subject: [PATCH 16/24] Add breadcrumb to navlink locator --- _playwright-tests/UI/helpers/navHelpers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_playwright-tests/UI/helpers/navHelpers.ts b/_playwright-tests/UI/helpers/navHelpers.ts index 66f9c4569..917f6e162 100644 --- a/_playwright-tests/UI/helpers/navHelpers.ts +++ b/_playwright-tests/UI/helpers/navHelpers.ts @@ -31,7 +31,7 @@ export const navigateToRepositories = async (page: Page) => { await page.route('https://smetrics.redhat.com/**', (route) => route.abort()); const repositoriesNavLink = page - .getByRole('navigation') + .getByRole('navigation', { name: 'Breadcrumb' }) .getByRole('link', { name: 'Repositories' }); await repositoriesNavLink.waitFor({ state: 'visible', timeout: 1500 }); await repositoriesNavLink.click(); From cf70de0be77c3df09a85d3e3eb58f6a2eb5f6eb3 Mon Sep 17 00:00:00 2001 From: swadeley Date: Wed, 21 May 2025 13:20:50 +0800 Subject: [PATCH 17/24] Add two user RBAC test --- _playwright-tests/UI/TwoUserRBAC.spec.ts | 148 ++++++++++++++--------- 1 file changed, 91 insertions(+), 57 deletions(-) diff --git a/_playwright-tests/UI/TwoUserRBAC.spec.ts b/_playwright-tests/UI/TwoUserRBAC.spec.ts index f3df04f94..655923f6f 100644 --- a/_playwright-tests/UI/TwoUserRBAC.spec.ts +++ b/_playwright-tests/UI/TwoUserRBAC.spec.ts @@ -1,70 +1,104 @@ -import { test, expect } from '@playwright/test'; -import { navigateToRepositories } from './helpers/navHelpers'; -import { closePopupsIfExist, getRowByNameOrUrl } from './helpers/helpers'; -import { randomUrl } from './helpers/repoHelpers'; +import { expect, test } from "@playwright/test"; +import { navigateToRepositories } from "../UI/helpers/navHelpers"; +import { randomName, randomUrl } from "../UI/helpers/repoHelpers"; +import { closePopupsIfExist, getRowByNameOrUrl } from "../UI/helpers/helpers"; +import fs from "fs"; +import { deleteAllRepos } from "../UI/helpers/deleteRepositories"; -export const url = randomUrl(); +const repoNamePrefix = "Repo-RBAC"; +const repoNameFile = "repoName.txt"; -export const repoNamePrefix = 'Repo-RBAC'; -export const randomName = () => (Math.random() + 1).toString(36).substring(2, 6); -export const repoName = `${repoNamePrefix}-${randomName()}`; +// Function to get or generate repo name using file persistence +const getRepoName = (): string => { + if (fs.existsSync(repoNameFile)) { + const repoName = fs.readFileSync(repoNameFile, "utf8"); + console.log(`Loaded repo name from file: ${repoName}`); + return repoName; + } + const repoName = `${repoNamePrefix}-${randomName()}`; + fs.writeFileSync(repoNameFile, repoName); + console.log(`Generated and saved repo name: ${repoName}`); + return repoName; +}; -test.describe('User Permissions Test', () => { - test('Default user configures repo', async ({ browser }) => { - const context = await browser.newContext({ storageState: '.auth/default_user.json' }); - const page = await context.newPage(); - // await deleteAllRepos(page, `&search=${repoNamePrefix}`); - await navigateToRepositories(page); - await closePopupsIfExist(page); - await test.step('Create a repository', async () => { - // Click on the 'Add repositories' button - // HMS-5268 There are two buttons on the ZeroState page - await page.getByRole('button', { name: 'Add repositories' }).first().click(); - await expect(page.getByRole('dialog', { name: 'Add custom repositories' })).toBeVisible(); - // Fill in the repository details - await page.getByLabel('Name').fill(repoName); - await page.getByLabel('Introspect only').click(); - await page.getByLabel('URL').fill(url); - await page.getByRole('button', { name: 'Save', exact: true }).click(); +const url = randomUrl(); + +test.describe("Combined user tests", () => { + test("Login as user 1 (admin)", async ({ page }) => { + await test.step("Navigate to the repository page", async () => { + // Clean up the repo name file + if (fs.existsSync(repoNameFile)) { + fs.unlinkSync(repoNameFile); + }; + console.log("Cleaned up repoName.txt"); + console.log("\n Try to delete old repos\n"); + await deleteAllRepos(page, `&search=${repoNamePrefix}`); + await navigateToRepositories(page); + await closePopupsIfExist(page); + }); + + await test.step("Create a repository", async () => { + await page + .getByRole("button", { name: "Add repositories" }) + .first() + .click(); + await expect( + page.getByRole("dialog", { name: "Add custom repositories" }) + ).toBeVisible(); + + const repoName = getRepoName(); + await page.getByLabel("Name").fill(repoName); + await page.getByLabel("Introspect only").click(); + await page.getByLabel("URL").fill(url); + await page.getByRole("button", { name: "Save", exact: true }).click(); }); - await test.step('Read the repo', async () => { - // Search for the created repo - await page.getByRole('textbox', { name: 'Filter by name/url' }).fill(repoName); + + await test.step("Read the repo", async () => { + const repoName = getRepoName(); const row = await getRowByNameOrUrl(page, repoName); - await expect(row.getByText('Valid')).toBeVisible(); - await row.getByLabel('Kebab toggle').click(); - // Click on the Edit button to see the repo - await row.getByRole('menuitem', { name: 'Edit' }).click(); - await expect(page.getByRole('dialog', { name: 'Edit custom repository' })).toBeVisible(); - // Assert we can read some values - await expect(page.getByPlaceholder('Enter name', { exact: true })).toHaveValue(repoName); - await expect(page.getByPlaceholder('https://', { exact: true })).toHaveValue(url); + await expect(row.getByText("Valid")).toBeVisible(); + await row.getByLabel("Kebab toggle").click(); + await row.getByRole("menuitem", { name: "Edit" }).click(); + await expect( + page.getByRole("dialog", { name: "Edit custom repository" }) + ).toBeVisible(); + await expect( + page.getByPlaceholder("Enter name", { exact: true }) + ).toHaveValue(repoName); + await expect( + page.getByPlaceholder("https://", { exact: true }) + ).toHaveValue(url); }); - await test.step('Update the repository', async () => { - await page.getByPlaceholder('Enter name', { exact: true }).fill(`${repoName}-Edited`); - await page.getByRole('button', { name: 'Save changes', exact: true }).click(); + + await test.step("Update the repository", async () => { + const repoName = getRepoName(); + await page + .getByPlaceholder("Enter name", { exact: true }) + .fill(`${repoName}-Edited`); + await page + .getByRole("button", { name: "Save changes", exact: true }) + .click(); }); - await page.context().clearCookies(); - await page.context().close(); }); - test('Read-only user can view but not edit', async ({ browser }) => { - const context = await browser.newContext({ storageState: '.auth/readonly_user.json' }); - const page = await context.newPage(); - await navigateToRepositories(page); - await closePopupsIfExist(page); + test( + "Login as user 2 (read-only)", + { tag: "@read-only" }, + async ({ page }) => { + await test.step("Navigate to the repository page", async () => { + await navigateToRepositories(page); + await closePopupsIfExist(page); + }); - // Assert read-only user can list but not edit previously created repo - const row = await getRowByNameOrUrl(page, repoName); - await expect(row.getByText('Valid')).toBeVisible(); - await row.getByLabel('Kebab toggle').click(); - // Assert we cannot click on the Edit button to see the repo - await expect(row.locator('.pf-v5-c-menu__list')).toBeVisible({ timeout: 5000 }); // Confirm menu is open - await expect(row.getByRole('menuitem', { name: 'Edit' })).not.toBeVisible({ timeout: 1000 }); - // You do not have the required permissions to perform this action. - const repoButton = page.getByRole('button', { name: 'Add repositories', exact: true }); - await expect(repoButton).toBeDisabled(); // Verify button is disabled - }); + await test.step("Read the repo", async () => { + const repoName = getRepoName(); + const row = await getRowByNameOrUrl(page, `${repoName}-Edited`); + await expect(row.getByText("Valid")).toBeVisible({ timeout: 60000 }); + await row.getByLabel("Kebab toggle").click(); + await expect(row.getByRole("menuitem", { name: "Edit" })).not.toBeVisible(); + }); + } + ); }); From 10d0c780c92d00ef09ba562002ea3ba326079075 Mon Sep 17 00:00:00 2001 From: swadeley Date: Wed, 21 May 2025 13:45:15 +0800 Subject: [PATCH 18/24] copy loginHelpers from integration repo --- _playwright-tests/helpers/loginHelpers.ts | 160 +++++++++++++++++++++ _playwright-tests/helpers/loginHelpers.tsx | 79 ---------- 2 files changed, 160 insertions(+), 79 deletions(-) create mode 100644 _playwright-tests/helpers/loginHelpers.ts delete mode 100644 _playwright-tests/helpers/loginHelpers.tsx diff --git a/_playwright-tests/helpers/loginHelpers.ts b/_playwright-tests/helpers/loginHelpers.ts new file mode 100644 index 000000000..bf0239f40 --- /dev/null +++ b/_playwright-tests/helpers/loginHelpers.ts @@ -0,0 +1,160 @@ +import { expect, type Page } from "@playwright/test"; +import path from "path"; +import fs from "fs"; + +export const logout = async (page: Page) => { + const button = await page + .locator(".pf-v6-c-menu-toggle.data-hj-suppress.sentry-mask") + .first(); + + await button.click(); + + await expect(async () => + page.getByRole("menuitem", { name: "Log out" }).isVisible() + ).toPass(); + + await page.getByRole("menuitem", { name: "Log out" }).click(); + + await expect(async () => { + expect(page.url()).not.toBe("/insights/content/repositories"); + }).toPass(); +}; + +// Inline reading and parsing of the JSON file +const queryJsonFile = (filePath: string) => { + try { + const data = fs.readFileSync(filePath, "utf-8"); // Read the file synchronously + const jsonData = JSON.parse(data); // Parse the JSON data + return jsonData; // Return the parsed JSON data + } catch (error) { + console.error("Error reading or parsing the JSON file:", error); + return null; + } +}; + +export const switchToUser = async (page: Page, userName: string) => { + const storagePath = path.join(__dirname, `../../.auth/${userName}.json`); + const storedData = queryJsonFile(storagePath); + + const jwtCookie = storedData.cookies.find( + (cookie: { name: string }) => cookie.name === "cs_jwt" + ); + if (!jwtCookie || !jwtCookie.value) { + throw new Error( + `No valid cs_jwt cookie found in storage state for user ${userName} at ${storagePath}` + ); + } + + // This is the main thing that this function does, sets the jwt for the API! + process.env.TOKEN = `Bearer ${jwtCookie.value}`; + await page.waitForTimeout(100); +}; + +export const storeUserAuth = async (page: Page, userName: string) => { + const storagePath = path.join(__dirname, `../../.auth/${userName}.json`); + // this stores the data in the json file at .auth/xxxx.json + await page.context().storageState({ + path: storagePath, + }); +}; + +export const logInWithUsernameAndPassword = async ( + page: Page, + username?: string, + password?: string +) => { + if (!username || !password) { + throw new Error("Username or password not found"); + } + + await page.goto("/insights/content/repositories"); + + await expect(async () => { + expect(page.url()).not.toBe( + process.env.BASE_URL + "/insights/content/repositories" + ); + }).toPass(); + + await expect(async () => + expect(page.getByText("Log in to your Red Hat account")).toBeVisible() + ).toPass(); + const login = page.getByRole("textbox"); + await login.fill(username); + await login.press("Enter"); + const passwordField = page.getByRole("textbox", { name: "Password" }); + await passwordField.fill(password); + await passwordField.press("Enter"); + + + await expect( + page.getByText('View all repositories within your organization.') + .or(page.getByText('Add repositories now', { exact: true })) + ).toBeVisible(); + + await storeUserAuth(page, username); +}; + +export const closePopupsIfExist = async (page: Page) => { + const locatorsToCheck = [ + page.locator('[class*="c-modal-box__close"] > button'), + page.locator('[class*="c-alert"][class*="notification-item"] button'), // This closes all toast pop-ups + page.locator(`button[id^="pendo-close-guide-"]`), // This closes the pendo guide pop-up + page.locator(`button[id="truste-consent-button"]`), // This closes the trusted consent pup-up + page.getByLabel("close-notification"), // This closes a one off info notification (May be covered by the toast above, needs recheck.) + ]; + + for (const locator of locatorsToCheck) { + await page.addLocatorHandler(locator, async () => { + await locator.click(); + }); + } +}; + +export const throwIfMissingEnvVariables = () => { + const ManditoryEnvVariables = [ + "ADMIN_USERNAME", + "ADMIN_PASSWORD", + "BASE_URL", + "ORG_ID_1", + "ACTIVATION_KEY_1", + ]; + + if (!process.env.PROD) ManditoryEnvVariables.push("PROXY"); + + const missing: string[] = []; + ManditoryEnvVariables.forEach((envVar) => { + if (!process.env[envVar]) { + missing.push(envVar); + } + }); + + if (missing.length > 0) { + throw new Error("Missing env variables:" + missing.join(",")); + } + + if (process.env.PROXY && process.env.BASE_URL?.includes("stage.foo.redhat")) { + throw new Error( + "If testing against a local machine you need to unset '' your proxy in the .env file!" + ); + } +}; + +export const ensureNotInPreview = async (page: Page) => { + const toggle = page.locator(".pf-v6-c-switch__toggle"); + if ((await toggle.isVisible()) && (await toggle.isChecked())) { + await toggle.click(); + } +}; + +export const ensureInPreview = async (page: Page) => { + const toggle = page.locator(".pf-v6-c-switch__toggle"); + await expect(toggle).toBeVisible(); + if (!(await toggle.isChecked())) { + await toggle.click(); + } + const turnOnButton = page.getByRole("button", { name: "Turn on" }); + if (await turnOnButton.isVisible()) { + await turnOnButton.click(); + } + await expect(toggle).toBeChecked(); +}; diff --git a/_playwright-tests/helpers/loginHelpers.tsx b/_playwright-tests/helpers/loginHelpers.tsx deleted file mode 100644 index 74d0399c0..000000000 --- a/_playwright-tests/helpers/loginHelpers.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import { expect, type Page } from '@playwright/test'; -import path from 'path'; - -// This file can only contain functions that are referenced by authentication. - -export const logout = async (page: Page) => { - const button = await page.locator( - 'div.pf-v6-c-toolbar__item.pf-m-hidden.pf-m-visible-on-lg.pf-v6-u-mr-0 > button', - ); - - await button.click(); - - await expect(async () => page.getByRole('menuitem', { name: 'Log out' }).isVisible()).toPass(); - - await page.getByRole('menuitem', { name: 'Log out' }).click(); - - await expect(async () => { - expect(page.url()).not.toBe('/insights/content/repositories'); - }).toPass(); - await expect(async () => - expect(page.getByText('Log in to your Red Hat account')).toBeVisible(), - ).toPass(); -}; - -export const logInWithUsernameAndPassword = async ( - page: Page, - username?: string, - password?: string, -) => { - if (!username || !password) { - throw new Error('Username or password not found'); - } - - await page.goto('/insights/content/repositories'); - - await expect(async () => - expect(page.getByText('Log in to your Red Hat account')).toBeVisible(), - ).toPass(); - - const login = page.getByRole('textbox'); - await login.fill(username); - await login.press('Enter'); - const passwordField = page.getByRole('textbox', { name: 'Password' }); - await passwordField.fill(password); - await passwordField.press('Enter'); - - await expect(async () => { - expect(page.url()).toBe(`${process.env.BASE_URL}/insights/content/repositories`); - }).toPass({ - intervals: [1_000], - timeout: 30_000, - }); -}; - -export const logInWithUser1 = async (page: Page) => - await logInWithUsernameAndPassword(page, process.env.USER1USERNAME, process.env.USER1PASSWORD); - -export const storeStorageStateAndToken = async (page: Page) => { - const { cookies } = await page - .context() - .storageState({ path: path.join(__dirname, '../../.auth/default_user.json') }); - process.env.TOKEN = `Bearer ${cookies.find((cookie) => cookie.name === 'cs_jwt')?.value}`; - await page.waitForTimeout(100); -}; - -export const throwIfMissingEnvVariables = () => { - const ManditoryEnvVariables = ['USER1USERNAME', 'USER1PASSWORD', 'BASE_URL']; - - const missing: string[] = []; - ManditoryEnvVariables.forEach((envVar) => { - if (!process.env[envVar]) { - missing.push(envVar); - } - }); - - if (missing.length > 0) { - throw new Error('Missing env variables:' + missing.join(',')); - } -}; From 8db6c31e75a0a3caa443ce9c8ea917674d8a101c Mon Sep 17 00:00:00 2001 From: swadeley Date: Wed, 21 May 2025 13:46:57 +0800 Subject: [PATCH 19/24] add support switchToUser --- _playwright-tests/auth.setup.ts | 51 +++++++++++++++------------------ 1 file changed, 23 insertions(+), 28 deletions(-) diff --git a/_playwright-tests/auth.setup.ts b/_playwright-tests/auth.setup.ts index 3facb270b..b9433f712 100644 --- a/_playwright-tests/auth.setup.ts +++ b/_playwright-tests/auth.setup.ts @@ -1,44 +1,39 @@ -import { expect, test as setup } from '@playwright/test'; -import { closePopupsIfExist } from './UI/helpers/helpers'; +import { expect, test as setup, type Page } from '@playwright/test'; import { throwIfMissingEnvVariables, logInWithUsernameAndPassword, logout, } from './helpers/loginHelpers'; +import { describe } from 'node:test'; +import { closePopupsIfExist } from './UI/helpers/helpers'; -setup.describe('Setup', async () => { - setup.describe.configure({ retries: 3 }); - - setup('Ensure needed ENV variables exist', async () => { +describe('Setup', async () => { + setup('Ensure needed ENV variables exist', async ({}) => { expect(() => throwIfMissingEnvVariables()).not.toThrow(); }); - setup('Authenticate', async ({ page }) => { - setup.setTimeout(60_000); - + setup('Authenticate all the users', async ({ page }) => { await closePopupsIfExist(page); - await logInWithUsernameAndPassword(page, process.env.USER1USERNAME, process.env.USER1PASSWORD); - // Save admin user storage state - await page.context().storageState({ path: '.auth/default_user.json' }); - await logout(page); + await logInWithUsernameAndPassword( page, - process.env.RO_USER_USERNAME, - process.env.RO_USER_PASSWORD, + process.env.READONLY_USERNAME, + process.env.READONLY_PASSWORD, ); - // Save read-only user storage state - await page.context().storageState({ path: '.auth/readonly_user.json' }); + await logout(page); - // Example of how to add another user - // await logout(page) - // await logInWithUsernameAndPassword( - // page, - // process.env.USER2USERNAME, - // process.env.USER2PASSWORD - // ); - // Example of how to switch to said user - // await switchToUser(page, process.env.USER1USERNAME!); - // await ensureNotInPreview(page); - // Other users for other tests can be added below after logging out + + await logInWithUsernameAndPassword( + page, + process.env.ADMIN_USERNAME, + process.env.ADMIN_PASSWORD, + ); + + await switchToUser(page, process.env.ADMIN_USERNAME!); + + // We do this as we run admin tests first. }); }); +function switchToUser(page: Page, arg1: string) { + throw new Error('Function not implemented.'); +} From 33a19e23252a181e96642223ed57eebb0345c518 Mon Sep 17 00:00:00 2001 From: swadeley Date: Wed, 21 May 2025 20:51:08 +0800 Subject: [PATCH 20/24] ignore repoName.txt --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index f96d10249..6e778d5e1 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,4 @@ playwright/.cache/ .env .auth bin +repoName.txt From bd20f53c77f0adc370418a29c694713523e38aff Mon Sep 17 00:00:00 2001 From: swadeley Date: Wed, 21 May 2025 20:53:10 +0800 Subject: [PATCH 21/24] switchToUser2 --- switchToUser2.setup.ts | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 switchToUser2.setup.ts diff --git a/switchToUser2.setup.ts b/switchToUser2.setup.ts new file mode 100644 index 000000000..1b069eed4 --- /dev/null +++ b/switchToUser2.setup.ts @@ -0,0 +1,7 @@ +import { expect, test as setup, type Page } from "@playwright/test"; +import { throwIfMissingEnvVariables, switchToUser } from './helpers/loginHelpers'; +import { describe } from "node:test"; + +setup("Switch to user 2", async ({ page }) => { + await switchToUser(page, process.env.READONLY_USERNAME!); +}); From 1c4ab96485c0f81489e49befa523394be62568e5 Mon Sep 17 00:00:00 2001 From: swadeley Date: Wed, 21 May 2025 20:55:43 +0800 Subject: [PATCH 22/24] swithToUser --- _playwright-tests/auth.setup.ts | 4 +--- playwright.config.ts | 19 +++++++++++++++++-- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/_playwright-tests/auth.setup.ts b/_playwright-tests/auth.setup.ts index b9433f712..08d37e122 100644 --- a/_playwright-tests/auth.setup.ts +++ b/_playwright-tests/auth.setup.ts @@ -3,6 +3,7 @@ import { throwIfMissingEnvVariables, logInWithUsernameAndPassword, logout, + switchToUser, } from './helpers/loginHelpers'; import { describe } from 'node:test'; import { closePopupsIfExist } from './UI/helpers/helpers'; @@ -34,6 +35,3 @@ describe('Setup', async () => { // We do this as we run admin tests first. }); }); -function switchToUser(page: Page, arg1: string) { - throw new Error('Function not implemented.'); -} diff --git a/playwright.config.ts b/playwright.config.ts index 7b2772a68..009a2e56e 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -53,12 +53,27 @@ export default defineConfig({ projects: [ { name: 'setup', testMatch: /.*\.setup\.ts/ }, { - name: 'chromium', + name: 'AdminTests', // 'Run admin user tests', + grepInvert: [/read-only/], // !!process.env.PROD ? [/preview-only/, /switch-to-preview/], ] : [/switch-to-preview/], use: { ...devices['Desktop Chrome'], - storageState: '.auth/default_user.json', + storageState: `./.auth/${process.env.ADMIN_USERNAME}.json`, }, dependencies: ['setup'], }, + { + name: "SwitchToUser2", + testMatch: /.switchToUser2\.setup\.ts/, + dependencies: ["setup"], + }, + { + name: "ReadOnlyTests", // 'Run read-only user tests', + grep: [/read-only/], + use: { + ...devices["Desktop Chrome"], + storageState: `.auth/${process.env.READONLY_USERNAME}.json`, + }, + dependencies: ["SwitchToUser2"], + }, ], }); From e5798c0ec9e497fffbde231ceffa3e4e78a043ec Mon Sep 17 00:00:00 2001 From: swadeley Date: Wed, 21 May 2025 20:57:14 +0800 Subject: [PATCH 23/24] lint fixes --- _playwright-tests/UI/TwoUserRBAC.spec.ts | 109 +++++++++------------- _playwright-tests/helpers/loginHelpers.ts | 82 ++++++++-------- 2 files changed, 80 insertions(+), 111 deletions(-) diff --git a/_playwright-tests/UI/TwoUserRBAC.spec.ts b/_playwright-tests/UI/TwoUserRBAC.spec.ts index 655923f6f..f7dfa738c 100644 --- a/_playwright-tests/UI/TwoUserRBAC.spec.ts +++ b/_playwright-tests/UI/TwoUserRBAC.spec.ts @@ -1,17 +1,17 @@ -import { expect, test } from "@playwright/test"; -import { navigateToRepositories } from "../UI/helpers/navHelpers"; -import { randomName, randomUrl } from "../UI/helpers/repoHelpers"; -import { closePopupsIfExist, getRowByNameOrUrl } from "../UI/helpers/helpers"; -import fs from "fs"; -import { deleteAllRepos } from "../UI/helpers/deleteRepositories"; +import { expect, test } from '@playwright/test'; +import { navigateToRepositories } from '../UI/helpers/navHelpers'; +import { randomName, randomUrl } from '../UI/helpers/repoHelpers'; +import { closePopupsIfExist, getRowByNameOrUrl } from '../UI/helpers/helpers'; +import fs from 'fs'; +import { deleteAllRepos } from '../UI/helpers/deleteRepositories'; -const repoNamePrefix = "Repo-RBAC"; -const repoNameFile = "repoName.txt"; +const repoNamePrefix = 'Repo-RBAC'; +const repoNameFile = 'repoName.txt'; // Function to get or generate repo name using file persistence const getRepoName = (): string => { if (fs.existsSync(repoNameFile)) { - const repoName = fs.readFileSync(repoNameFile, "utf8"); + const repoName = fs.readFileSync(repoNameFile, 'utf8'); console.log(`Loaded repo name from file: ${repoName}`); return repoName; } @@ -21,84 +21,61 @@ const getRepoName = (): string => { return repoName; }; - - const url = randomUrl(); -test.describe("Combined user tests", () => { - test("Login as user 1 (admin)", async ({ page }) => { - await test.step("Navigate to the repository page", async () => { +test.describe('Combined user tests', () => { + test('Login as user 1 (admin)', async ({ page }) => { + await test.step('Navigate to the repository page', async () => { // Clean up the repo name file if (fs.existsSync(repoNameFile)) { fs.unlinkSync(repoNameFile); - }; - console.log("Cleaned up repoName.txt"); - console.log("\n Try to delete old repos\n"); + } await deleteAllRepos(page, `&search=${repoNamePrefix}`); await navigateToRepositories(page); await closePopupsIfExist(page); }); - await test.step("Create a repository", async () => { - await page - .getByRole("button", { name: "Add repositories" }) - .first() - .click(); - await expect( - page.getByRole("dialog", { name: "Add custom repositories" }) - ).toBeVisible(); + await test.step('Create a repository', async () => { + await page.getByRole('button', { name: 'Add repositories' }).first().click(); + await expect(page.getByRole('dialog', { name: 'Add custom repositories' })).toBeVisible(); const repoName = getRepoName(); - await page.getByLabel("Name").fill(repoName); - await page.getByLabel("Introspect only").click(); - await page.getByLabel("URL").fill(url); - await page.getByRole("button", { name: "Save", exact: true }).click(); + await page.getByLabel('Name').fill(repoName); + await page.getByLabel('Introspect only').click(); + await page.getByLabel('URL').fill(url); + await page.getByRole('button', { name: 'Save', exact: true }).click(); }); - await test.step("Read the repo", async () => { + await test.step('Read the repo', async () => { const repoName = getRepoName(); const row = await getRowByNameOrUrl(page, repoName); - await expect(row.getByText("Valid")).toBeVisible(); - await row.getByLabel("Kebab toggle").click(); - await row.getByRole("menuitem", { name: "Edit" }).click(); - await expect( - page.getByRole("dialog", { name: "Edit custom repository" }) - ).toBeVisible(); - await expect( - page.getByPlaceholder("Enter name", { exact: true }) - ).toHaveValue(repoName); - await expect( - page.getByPlaceholder("https://", { exact: true }) - ).toHaveValue(url); + await expect(row.getByText('Valid')).toBeVisible(); + await row.getByLabel('Kebab toggle').click(); + await row.getByRole('menuitem', { name: 'Edit' }).click(); + await expect(page.getByRole('dialog', { name: 'Edit custom repository' })).toBeVisible(); + await expect(page.getByPlaceholder('Enter name', { exact: true })).toHaveValue(repoName); + await expect(page.getByPlaceholder('https://', { exact: true })).toHaveValue(url); }); - await test.step("Update the repository", async () => { + await test.step('Update the repository', async () => { const repoName = getRepoName(); - await page - .getByPlaceholder("Enter name", { exact: true }) - .fill(`${repoName}-Edited`); - await page - .getByRole("button", { name: "Save changes", exact: true }) - .click(); + await page.getByPlaceholder('Enter name', { exact: true }).fill(`${repoName}-Edited`); + await page.getByRole('button', { name: 'Save changes', exact: true }).click(); }); }); - test( - "Login as user 2 (read-only)", - { tag: "@read-only" }, - async ({ page }) => { - await test.step("Navigate to the repository page", async () => { - await navigateToRepositories(page); - await closePopupsIfExist(page); - }); + test('Login as user 2 (read-only)', { tag: '@read-only' }, async ({ page }) => { + await test.step('Navigate to the repository page', async () => { + await navigateToRepositories(page); + await closePopupsIfExist(page); + }); - await test.step("Read the repo", async () => { - const repoName = getRepoName(); - const row = await getRowByNameOrUrl(page, `${repoName}-Edited`); - await expect(row.getByText("Valid")).toBeVisible({ timeout: 60000 }); - await row.getByLabel("Kebab toggle").click(); - await expect(row.getByRole("menuitem", { name: "Edit" })).not.toBeVisible(); - }); - } - ); + await test.step('Read the repo', async () => { + const repoName = getRepoName(); + const row = await getRowByNameOrUrl(page, `${repoName}-Edited`); + await expect(row.getByText('Valid')).toBeVisible({ timeout: 60000 }); + await row.getByLabel('Kebab toggle').click(); + await expect(row.getByRole('menuitem', { name: 'Edit' })).not.toBeVisible(); + }); + }); }); diff --git a/_playwright-tests/helpers/loginHelpers.ts b/_playwright-tests/helpers/loginHelpers.ts index bf0239f40..ad27eca7b 100644 --- a/_playwright-tests/helpers/loginHelpers.ts +++ b/_playwright-tests/helpers/loginHelpers.ts @@ -1,33 +1,29 @@ -import { expect, type Page } from "@playwright/test"; -import path from "path"; -import fs from "fs"; +import { expect, type Page } from '@playwright/test'; +import path from 'path'; +import fs from 'fs'; export const logout = async (page: Page) => { - const button = await page - .locator(".pf-v6-c-menu-toggle.data-hj-suppress.sentry-mask") - .first(); + const button = await page.locator('.pf-v6-c-menu-toggle.data-hj-suppress.sentry-mask').first(); await button.click(); - await expect(async () => - page.getByRole("menuitem", { name: "Log out" }).isVisible() - ).toPass(); + await expect(async () => page.getByRole('menuitem', { name: 'Log out' }).isVisible()).toPass(); - await page.getByRole("menuitem", { name: "Log out" }).click(); + await page.getByRole('menuitem', { name: 'Log out' }).click(); await expect(async () => { - expect(page.url()).not.toBe("/insights/content/repositories"); + expect(page.url()).not.toBe('/insights/content/repositories'); }).toPass(); }; // Inline reading and parsing of the JSON file const queryJsonFile = (filePath: string) => { try { - const data = fs.readFileSync(filePath, "utf-8"); // Read the file synchronously + const data = fs.readFileSync(filePath, 'utf-8'); // Read the file synchronously const jsonData = JSON.parse(data); // Parse the JSON data return jsonData; // Return the parsed JSON data } catch (error) { - console.error("Error reading or parsing the JSON file:", error); + console.error('Error reading or parsing the JSON file:', error); return null; } }; @@ -36,12 +32,10 @@ export const switchToUser = async (page: Page, userName: string) => { const storagePath = path.join(__dirname, `../../.auth/${userName}.json`); const storedData = queryJsonFile(storagePath); - const jwtCookie = storedData.cookies.find( - (cookie: { name: string }) => cookie.name === "cs_jwt" - ); + const jwtCookie = storedData.cookies.find((cookie: { name: string }) => cookie.name === 'cs_jwt'); if (!jwtCookie || !jwtCookie.value) { throw new Error( - `No valid cs_jwt cookie found in storage state for user ${userName} at ${storagePath}` + `No valid cs_jwt cookie found in storage state for user ${userName} at ${storagePath}`, ); } @@ -61,35 +55,33 @@ export const storeUserAuth = async (page: Page, userName: string) => { export const logInWithUsernameAndPassword = async ( page: Page, username?: string, - password?: string + password?: string, ) => { if (!username || !password) { - throw new Error("Username or password not found"); + throw new Error('Username or password not found'); } - await page.goto("/insights/content/repositories"); + await page.goto('/insights/content/repositories'); await expect(async () => { - expect(page.url()).not.toBe( - process.env.BASE_URL + "/insights/content/repositories" - ); + expect(page.url()).not.toBe(process.env.BASE_URL + '/insights/content/repositories'); }).toPass(); await expect(async () => - expect(page.getByText("Log in to your Red Hat account")).toBeVisible() + expect(page.getByText('Log in to your Red Hat account')).toBeVisible(), ).toPass(); - const login = page.getByRole("textbox"); + const login = page.getByRole('textbox'); await login.fill(username); - await login.press("Enter"); - const passwordField = page.getByRole("textbox", { name: "Password" }); + await login.press('Enter'); + const passwordField = page.getByRole('textbox', { name: 'Password' }); await passwordField.fill(password); - await passwordField.press("Enter"); - + await passwordField.press('Enter'); await expect( - page.getByText('View all repositories within your organization.') - .or(page.getByText('Add repositories now', { exact: true })) - ).toBeVisible(); + page + .getByText('View all repositories within your organization.') + .or(page.getByText('Add repositories now', { exact: true })), + ).toBeVisible(); await storeUserAuth(page, username); }; @@ -100,7 +92,7 @@ export const closePopupsIfExist = async (page: Page) => { page.locator('[class*="c-alert"][class*="notification-item"] button'), // This closes all toast pop-ups page.locator(`button[id^="pendo-close-guide-"]`), // This closes the pendo guide pop-up page.locator(`button[id="truste-consent-button"]`), // This closes the trusted consent pup-up - page.getByLabel("close-notification"), // This closes a one off info notification (May be covered by the toast above, needs recheck.) + page.getByLabel('close-notification'), // This closes a one off info notification (May be covered by the toast above, needs recheck.) ]; for (const locator of locatorsToCheck) { @@ -112,14 +104,14 @@ export const closePopupsIfExist = async (page: Page) => { export const throwIfMissingEnvVariables = () => { const ManditoryEnvVariables = [ - "ADMIN_USERNAME", - "ADMIN_PASSWORD", - "BASE_URL", - "ORG_ID_1", - "ACTIVATION_KEY_1", + 'ADMIN_USERNAME', + 'ADMIN_PASSWORD', + 'BASE_URL', + 'ORG_ID_1', + 'ACTIVATION_KEY_1', ]; - if (!process.env.PROD) ManditoryEnvVariables.push("PROXY"); + if (!process.env.PROD) ManditoryEnvVariables.push('PROXY'); const missing: string[] = []; ManditoryEnvVariables.forEach((envVar) => { @@ -129,30 +121,30 @@ export const throwIfMissingEnvVariables = () => { }); if (missing.length > 0) { - throw new Error("Missing env variables:" + missing.join(",")); + throw new Error('Missing env variables:' + missing.join(',')); } - if (process.env.PROXY && process.env.BASE_URL?.includes("stage.foo.redhat")) { + if (process.env.PROXY && process.env.BASE_URL?.includes('stage.foo.redhat')) { throw new Error( - "If testing against a local machine you need to unset '' your proxy in the .env file!" + "If testing against a local machine you need to unset '' your proxy in the .env file!", ); } }; export const ensureNotInPreview = async (page: Page) => { - const toggle = page.locator(".pf-v6-c-switch__toggle"); + const toggle = page.locator('.pf-v6-c-switch__toggle'); if ((await toggle.isVisible()) && (await toggle.isChecked())) { await toggle.click(); } }; export const ensureInPreview = async (page: Page) => { - const toggle = page.locator(".pf-v6-c-switch__toggle"); + const toggle = page.locator('.pf-v6-c-switch__toggle'); await expect(toggle).toBeVisible(); if (!(await toggle.isChecked())) { await toggle.click(); } - const turnOnButton = page.getByRole("button", { name: "Turn on" }); + const turnOnButton = page.getByRole('button', { name: 'Turn on' }); if (await turnOnButton.isVisible()) { await turnOnButton.click(); } From 15cc5d521a882d5d466695ae724ded4cb55d970d Mon Sep 17 00:00:00 2001 From: swadeley Date: Wed, 21 May 2025 20:57:40 +0800 Subject: [PATCH 24/24] increase nav timeout --- _playwright-tests/UI/helpers/navHelpers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_playwright-tests/UI/helpers/navHelpers.ts b/_playwright-tests/UI/helpers/navHelpers.ts index 917f6e162..3c64e6104 100644 --- a/_playwright-tests/UI/helpers/navHelpers.ts +++ b/_playwright-tests/UI/helpers/navHelpers.ts @@ -2,7 +2,7 @@ import { type Page } from '@playwright/test'; import { retry } from './helpers'; const navigateToRepositoriesFunc = async (page: Page) => { - await page.goto('/insights/content/repositories', { timeout: 10001 }); + await page.goto('/insights/content/repositories', { timeout: 20000 }); const zeroState = page.getByText('Start using Content management now');