From d9253a4ed6dd00e09a2814225a333405a23180ba Mon Sep 17 00:00:00 2001 From: swadeley Date: Thu, 24 Apr 2025 16:19:23 +0100 Subject: [PATCH 01/14] configure read-only and admin users --- playwright.config.ts | 160 ++++++++++++++++++++++++------------------- tests/auth.setup.ts | 20 +++++- 2 files changed, 105 insertions(+), 75 deletions(-) diff --git a/playwright.config.ts b/playwright.config.ts index bda2f8a..65da889 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -1,80 +1,96 @@ -import { defineConfig, devices } from '@playwright/test'; -import 'dotenv/config'; +import { defineConfig, devices } from "@playwright/test"; +import "dotenv/config"; /** * See https://playwright.dev/docs/test-configuration. */ export default defineConfig({ - testDir: './tests', - fullyParallel: false, - forbidOnly: false, - retries: process.env.CI ? 1 : 0, - workers: 1, - reporter: process.env.CI - ? [ - [ - 'playwright-ctrf-json-reporter', - { useDetails: true, outputDir: 'playwright-ctrf', outputFile: 'playwright-ctrf.json' }, - ], - ['html', { outputFolder: 'playwright-report' }], - ['@currents/playwright'], - ] - : 'list', - timeout: process.env.CI ? 60000 : 30000, - expect: { timeout: process.env.CI ? 60000 : 20000 }, - use: { - testIdAttribute: 'data-ouia-component-id', - launchOptions: { - args: ['--use-fake-device-for-media-stream'], - }, - ...(process.env.TOKEN - ? { - extraHTTPHeaders: { - Authorization: process.env.TOKEN, - }, - } - : {}), - baseURL: process.env.BASE_URL, - trace: 'on', - screenshot: 'on', - video: 'on', - ignoreHTTPSErrors: true, - ...process.env.PROXY ? { - proxy: { - server: process.env.PROXY, - } - } : {} + testDir: "./tests", + fullyParallel: false, + forbidOnly: false, + retries: process.env.CI ? 1 : 0, + workers: 1, + reporter: process.env.CI + ? [ + [ + "playwright-ctrf-json-reporter", + { + useDetails: true, + outputDir: "playwright-ctrf", + outputFile: "playwright-ctrf.json", + }, + ], + ["html", { outputFolder: "playwright-report" }], + ["@currents/playwright"], + ] + : "list", + timeout: process.env.CI ? 60000 : 30000, + expect: { timeout: process.env.CI ? 60000 : 20000 }, + use: { + testIdAttribute: "data-ouia-component-id", + launchOptions: { + args: ["--use-fake-device-for-media-stream"], }, - projects: [ - { name: 'setup', testMatch: /.*\.setup\.ts/ }, - { - name: 'chromium', - grepInvert: !!process.env.PROD ? [/preview-only/, /switch-to-preview/, /local-only/] : [/switch-to-preview/, /local-only/], - use: { - ...devices['Desktop Chrome'], - storageState: `.auth/${process.env.USER1USERNAME}.json`, - }, - dependencies: ['setup'], - }, - ...!!process.env.PROD ? - [{ - name: 'Switch to preview', - grep: [/switch-to-preview/], - use: { - ...devices['Desktop Chrome'], - storageState: `.auth/${process.env.USER1USERNAME}.json`, + ...(process.env.TOKEN + ? { + extraHTTPHeaders: { + Authorization: process.env.TOKEN, + }, + } + : {}), + baseURL: process.env.BASE_URL, + trace: "on", + screenshot: "on", + video: "on", + ignoreHTTPSErrors: true, + ...(process.env.PROXY + ? { + proxy: { + server: process.env.PROXY, + }, + } + : {}), + }, + projects: [ + { name: "setup", testMatch: /.*\.setup\.ts/ }, + { + name: "chromium", // 'Run admin user tests', + grepInvert: [/read-only/], // !!process.env.PROD ? [/preview-only/, /switch-to-preview/], ] : [/switch-to-preview/], + use: { + ...devices["Desktop Chrome"], + storageState: `.auth/${process.env.USER1USERNAME}.json`, + }, + dependencies: ["setup"], + }, + + { + name: "chromium", // 'Run read-only user tests', + grep: [/read-only/], + use: { + ...devices["Desktop Chrome"], + storageState: `.auth/${process.env.STAGE_RO_USER_USERNAME}.json`, + }, + dependencies: ["setup"], + }, + // ...!!process.env.PROD ? + // [{ + // name: 'Switch to preview', + // grep: [/switch-to-preview/], + // use: { + // ...devices['Desktop Chrome'], + // storageState: `.auth/${process.env.USER1USERNAME}.json`, - }, - dependencies: ['setup'],//'chromium', - }, - { - name: 'Run preview only', - grep: [/preview-only/], - use: { - ...devices['Desktop Chrome'], - storageState: `.auth/${process.env.USER1USERNAME}.json`, - }, - dependencies: ['Switch to preview'], - }] : [], - ], + // }, + // dependencies: ['setup'],//'chromium', + // }, + // { + // name: 'Run preview only', + // grep: [/preview-only/], + // use: { + // ...devices['Desktop Chrome'], + // storageState: `.auth/${process.env.USER1USERNAME}.json`, + // }, + // dependencies: ['Switch to preview'], + // }] : [], + ], }); diff --git a/tests/auth.setup.ts b/tests/auth.setup.ts index 251095f..b75cf7c 100644 --- a/tests/auth.setup.ts +++ b/tests/auth.setup.ts @@ -5,21 +5,35 @@ import { switchToUser, logInWithUsernameAndPassword, ensureNotInPreview, + logout, } from "./helpers/loginHelpers"; import { describe } from "node:test"; +const authFile = '.auth/contentPlaywrightUserAdmin.json'; +const authFileRO = '.auth/contentPlaywrightReader.json'; + describe("Setup", async () => { setup("Ensure needed ENV variables exist", async ({}) => { expect(() => throwIfMissingEnvVariables()).not.toThrow(); }); - setup("Authenticate user 1", async ({ page }) => { + setup("Authenticate all the users", async ({ page }) => { await closePopupsIfExist(page); await logInWithUsernameAndPassword( page, process.env.USER1USERNAME, process.env.USER1PASSWORD ); + await page.context().storageState({ path: authFile }); + + await logout(page); + await logInWithUsernameAndPassword( + page, + process.env.RO_USER_USERNAME, + process.env.RO_USER_PASSWORD + ); + await page.context().storageState({ path: authFileRO }); + await logout(page); // Example of how to add another user // await logout(page) // await logInWithUsernameAndPassword( @@ -28,8 +42,8 @@ describe("Setup", async () => { // process.env.USER2PASSWORD // ); // Example of how to switch to said user - await switchToUser(page, process.env.USER1USERNAME!); - await ensureNotInPreview(page); + // await switchToUser(page, process.env.USER1USERNAME!); + // await ensureNotInPreview(page); // Other users for other tests can be added below after logging out }); }); From 641fdfc52dca6bce2708cdc32ac37a8556062299 Mon Sep 17 00:00:00 2001 From: swadeley Date: Thu, 24 Apr 2025 16:19:56 +0100 Subject: [PATCH 02/14] Add two-user RBAC test --- tests/Integration/twouserRBACTest.spec.ts | 93 +++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 tests/Integration/twouserRBACTest.spec.ts diff --git a/tests/Integration/twouserRBACTest.spec.ts b/tests/Integration/twouserRBACTest.spec.ts new file mode 100644 index 0000000..ae08b82 --- /dev/null +++ b/tests/Integration/twouserRBACTest.spec.ts @@ -0,0 +1,93 @@ +import { expect, test } from "@playwright/test"; +import { navigateToRepositories } from "../UI/helpers/navHelpers"; +import { switchToUser } from "../helpers/loginHelpers"; +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'; + +// 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; +}; + +// Clean up the repo name file +test.beforeAll(async () => { + if (fs.existsSync(repoNameFile)) { + fs.unlinkSync(repoNameFile); + console.log('Cleaned up repoName.txt'); + } +}); + +const url = randomUrl(); + +test.describe.serial("Combined user tests", () => { + test("Login as user 1 (admin)", { tag: "@admin" }, async ({ page }) => { + await test.step('Navigate to the repository page', async () => { + await switchToUser(page, process.env.USER1USERNAME!); + 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 () => { + 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 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(); + }); + }); + + test("Login as user 2 (read-only)", { tag: "@read-only" }, async ({ page }) => { + await test.step('Navigate to the repository page', async () => { + await switchToUser(page, process.env.RO_USER_USERNAME!); + 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 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(); + }); + }); +}); \ No newline at end of file From 2b6ae92caddc4b5a28746a63029bef92aa77b4a5 Mon Sep 17 00:00:00 2001 From: swadeley Date: Thu, 1 May 2025 17:33:00 +0100 Subject: [PATCH 03/14] fix playwright.config --- playwright.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playwright.config.ts b/playwright.config.ts index 65da889..7e96cb5 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -68,7 +68,7 @@ export default defineConfig({ grep: [/read-only/], use: { ...devices["Desktop Chrome"], - storageState: `.auth/${process.env.STAGE_RO_USER_USERNAME}.json`, + storageState: `.auth/${process.env.RO_USER_USERNAME}.json`, }, dependencies: ["setup"], }, From 07b6796739775dfe9da108ebdedb7327607a5891 Mon Sep 17 00:00:00 2001 From: swadeley Date: Thu, 1 May 2025 17:35:58 +0100 Subject: [PATCH 04/14] Debug lines in auth.setup --- tests/auth.setup.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/auth.setup.ts b/tests/auth.setup.ts index b75cf7c..a283266 100644 --- a/tests/auth.setup.ts +++ b/tests/auth.setup.ts @@ -19,14 +19,25 @@ describe("Setup", async () => { setup("Authenticate all the users", async ({ page }) => { await closePopupsIfExist(page); + await expect(page.locator('body')).toBeVisible(); + console.log('Page URL before login:', page.url()); await logInWithUsernameAndPassword( page, process.env.USER1USERNAME, process.env.USER1PASSWORD ); + const cookies = await page.context().cookies(); + console.log('Cookies after Admin login:', cookies); + // wait longer to see if cookie is available + await expect( + page.getByRole('heading', { name: 'Repositories', exact: false }), + ).toBeVisible(); await page.context().storageState({ path: authFile }); + console.log('Cookies before Admin logout:', await page.context().cookies()); await logout(page); + console.log('Cookies after Admin logout:', await page.context().cookies()); + await logInWithUsernameAndPassword( page, process.env.RO_USER_USERNAME, From 6779f1c918d61949915a2507d5bdcd3e655337cf Mon Sep 17 00:00:00 2001 From: swadeley Date: Thu, 1 May 2025 17:36:32 +0100 Subject: [PATCH 05/14] debug lines in loginHelpers --- tests/helpers/loginHelpers.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/tests/helpers/loginHelpers.ts b/tests/helpers/loginHelpers.ts index 403401e..8d6f8c9 100644 --- a/tests/helpers/loginHelpers.ts +++ b/tests/helpers/loginHelpers.ts @@ -47,19 +47,25 @@ export const logInWithUsernameAndPassword = async ( await passwordField.press("Enter"); await expect(async () => { - expect(page.url()).toBe( + expect(page.url()).toContain( `${process.env.BASE_URL}/insights/content/repositories` ); }).toPass(); }; export const switchToUser = async (page: Page, userName: string) => { + const storagePath = path.join(__dirname, `../../.auth/${userName}.json`); const { cookies } = await page.context().storageState({ - path: path.join(__dirname, `../../.auth/${userName}.json`), + path: storagePath, }); - process.env.TOKEN = `Bearer ${ - cookies.find((cookie) => cookie.name === "cs_jwt")?.value - }`; + + const jwtCookie = cookies.find((cookie) => 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}`); + } + + process.env.TOKEN = `Bearer ${jwtCookie.value}`; await page.waitForTimeout(100); }; From 213dad858b705a94731ab9108816fe3356d95bdc Mon Sep 17 00:00:00 2001 From: swadeley Date: Wed, 21 May 2025 10:24:45 +0800 Subject: [PATCH 06/14] Revert "debug lines in loginHelpers" This reverts commit 6779f1c918d61949915a2507d5bdcd3e655337cf. --- tests/helpers/loginHelpers.ts | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/tests/helpers/loginHelpers.ts b/tests/helpers/loginHelpers.ts index 8d6f8c9..403401e 100644 --- a/tests/helpers/loginHelpers.ts +++ b/tests/helpers/loginHelpers.ts @@ -47,25 +47,19 @@ export const logInWithUsernameAndPassword = async ( await passwordField.press("Enter"); await expect(async () => { - expect(page.url()).toContain( + expect(page.url()).toBe( `${process.env.BASE_URL}/insights/content/repositories` ); }).toPass(); }; export const switchToUser = async (page: Page, userName: string) => { - const storagePath = path.join(__dirname, `../../.auth/${userName}.json`); const { cookies } = await page.context().storageState({ - path: storagePath, + path: path.join(__dirname, `../../.auth/${userName}.json`), }); - - const jwtCookie = cookies.find((cookie) => 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}`); - } - - process.env.TOKEN = `Bearer ${jwtCookie.value}`; + process.env.TOKEN = `Bearer ${ + cookies.find((cookie) => cookie.name === "cs_jwt")?.value + }`; await page.waitForTimeout(100); }; From 9ff77af34e5d4fb23e012c3f8268056523273485 Mon Sep 17 00:00:00 2001 From: swadeley Date: Wed, 21 May 2025 10:25:27 +0800 Subject: [PATCH 07/14] Revert "Debug lines in auth.setup" This reverts commit 07b6796739775dfe9da108ebdedb7327607a5891. --- tests/auth.setup.ts | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/tests/auth.setup.ts b/tests/auth.setup.ts index a283266..b75cf7c 100644 --- a/tests/auth.setup.ts +++ b/tests/auth.setup.ts @@ -19,25 +19,14 @@ describe("Setup", async () => { setup("Authenticate all the users", async ({ page }) => { await closePopupsIfExist(page); - await expect(page.locator('body')).toBeVisible(); - console.log('Page URL before login:', page.url()); await logInWithUsernameAndPassword( page, process.env.USER1USERNAME, process.env.USER1PASSWORD ); - const cookies = await page.context().cookies(); - console.log('Cookies after Admin login:', cookies); - // wait longer to see if cookie is available - await expect( - page.getByRole('heading', { name: 'Repositories', exact: false }), - ).toBeVisible(); await page.context().storageState({ path: authFile }); - console.log('Cookies before Admin logout:', await page.context().cookies()); await logout(page); - console.log('Cookies after Admin logout:', await page.context().cookies()); - await logInWithUsernameAndPassword( page, process.env.RO_USER_USERNAME, From c03626b77277c0d7e915c2c6d42815e131844178 Mon Sep 17 00:00:00 2001 From: adewar Date: Thu, 1 May 2025 14:34:56 -0600 Subject: [PATCH 08/14] Updated helper functions and config --- .github/workflows/stageTestAction.yml | 4 +- example.env | 7 +- playwright.config.ts | 38 ++----- readme.md | 15 +-- repoName.txt | 1 + tests/Integration/switchToPreview.spec.ts | 10 -- tests/Integration/twouserRBACTest.spec.ts | 119 +++++++++++++--------- tests/auth.setup.ts | 32 ++---- tests/helpers/loginHelpers.ts | 62 ++++++++--- tests/switchToUser2.setup.ts | 10 ++ 10 files changed, 163 insertions(+), 135 deletions(-) create mode 100644 repoName.txt delete mode 100644 tests/Integration/switchToPreview.spec.ts create mode 100644 tests/switchToUser2.setup.ts diff --git a/.github/workflows/stageTestAction.yml b/.github/workflows/stageTestAction.yml index b38c1be..8e176fa 100644 --- a/.github/workflows/stageTestAction.yml +++ b/.github/workflows/stageTestAction.yml @@ -41,8 +41,8 @@ jobs: - name: Create .env file run: | - echo "USER1USERNAME=$USER1USERNAME" >> .env - echo "USER1PASSWORD=$USER1PASSWORD" >> .env + echo "ADMIN_USERNAME=$ADMIN_USERNAME" >> .env + echo "ADMIN_PASSWORD=$ADMIN_PASSWORD" >> .env echo "BASE_URL=$BASE_URL" >> .env echo "PROXY=$PROXY" >> .env echo "TOKEN=apple" >> .env diff --git a/example.env b/example.env index dea756b..af84c18 100644 --- a/example.env +++ b/example.env @@ -1,8 +1,11 @@ #skus for qe user contentPlaywrightUserAdmin: # MCT4022,MCT3718,MCT3695,ES0113909 -USER1USERNAME="contentPlaywrightUserAdmin" # Required -USER1PASSWORD="" # Required (Ask Andrew if needed) +ADMIN_USERNAME="contentPlaywrightUserAdmin" +ADMIN_PASSWORD=SOMETHINGSECRET + +READONLY_USERNAME="contentPlaywrightReader" +READONLY_PASSWORD=SOMETHINGSECRET ORG_ID_1="1234" #org id to register for registration tests ACTIVATION_KEY_1="MyKey" #activation Key used for testing diff --git a/playwright.config.ts b/playwright.config.ts index 7e96cb5..ccce150 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -52,45 +52,29 @@ export default defineConfig({ : {}), }, projects: [ - { name: "setup", testMatch: /.*\.setup\.ts/ }, + { name: "setup", testMatch: /auth\.setup\.ts/ }, { - name: "chromium", // 'Run admin user tests', + 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/${process.env.USER1USERNAME}.json`, + storageState: `./.auth/${process.env.ADMIN_USERNAME}.json`, // Thise is setting the cookies }, dependencies: ["setup"], }, - { - name: "chromium", // 'Run read-only user tests', + 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.RO_USER_USERNAME}.json`, + storageState: `.auth/${process.env.READONLY_USERNAME}.json`, }, - dependencies: ["setup"], + dependencies: ["SwitchToUser2"], }, - // ...!!process.env.PROD ? - // [{ - // name: 'Switch to preview', - // grep: [/switch-to-preview/], - // use: { - // ...devices['Desktop Chrome'], - // storageState: `.auth/${process.env.USER1USERNAME}.json`, - - // }, - // dependencies: ['setup'],//'chromium', - // }, - // { - // name: 'Run preview only', - // grep: [/preview-only/], - // use: { - // ...devices['Desktop Chrome'], - // storageState: `.auth/${process.env.USER1USERNAME}.json`, - // }, - // dependencies: ['Switch to preview'], - // }] : [], ], }); diff --git a/readme.md b/readme.md index 117d6fa..023c867 100644 --- a/readme.md +++ b/readme.md @@ -19,20 +19,21 @@ yarn get-tests ## Podman As your user, run podman to serve the api: + ``` podman system service -t 0 unix:///tmp/podman.sock ``` Uncomment the DOCKER_SOCKET option in the .env file: + ``` DOCKER_SOCKET="/tmp/podman.sock" ``` ## Docker -* ensure the docker service is running -* ensure your user is part of the 'docker' user group - +- ensure the docker service is running +- ensure your user is part of the 'docker' user group # Option 1 Run local: @@ -40,8 +41,8 @@ For local testing, make sure your front-end/backend servers are running and acce - Ensure you do NOT specify a proxy in your .env file (put an empty value: "") - Make sure your .env's BASE_URL is pointed to the local front-end server "https://stage.foo.redhat.com:1337" -- USER1USERNAME="" -- USER1PASSWORD="" +- ADMIN_USERNAME="" +- ADMIN_PASSWORD="" Note: For Ethel, your user will require the following skus: MCT4022,MCT3718,MCT3695,ES0113909 @@ -51,8 +52,8 @@ For local stage testing, make sure the following: - PROXY must be set correctly in your .env file. - Make sure your .env's BASE_URL is pointed to the targeted env, if targeting PROD, the proxy is not needed. -- USER1USERNAME="" -- USER1PASSWORD="" +- ADMIN_USERNAME="" +- ADMIN_PASSWORD="" Note: For Ethel, your user will require the following skus: MCT4022,MCT3718,MCT3695,ES0113909 diff --git a/repoName.txt b/repoName.txt new file mode 100644 index 0000000..9068369 --- /dev/null +++ b/repoName.txt @@ -0,0 +1 @@ +Repo-RBAC-hms2 \ No newline at end of file diff --git a/tests/Integration/switchToPreview.spec.ts b/tests/Integration/switchToPreview.spec.ts deleted file mode 100644 index d5d9994..0000000 --- a/tests/Integration/switchToPreview.spec.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { expect, test } from "@playwright/test"; -import { navigateToRepositories } from "../UI/helpers/navHelpers"; -import { ensureInPreview } from "../helpers/loginHelpers"; - -test.describe("Switch to preview", { tag: "@switch-to-preview" }, async () => { - test("Click preview button", async ({ page }) => { - await navigateToRepositories(page); - await ensureInPreview(page) - }); -}); diff --git a/tests/Integration/twouserRBACTest.spec.ts b/tests/Integration/twouserRBACTest.spec.ts index ae08b82..e861fa0 100644 --- a/tests/Integration/twouserRBACTest.spec.ts +++ b/tests/Integration/twouserRBACTest.spec.ts @@ -1,20 +1,18 @@ 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 { switchToUser } from "../helpers/loginHelpers"; -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; } @@ -28,66 +26,89 @@ const getRepoName = (): string => { test.beforeAll(async () => { if (fs.existsSync(repoNameFile)) { fs.unlinkSync(repoNameFile); - console.log('Cleaned up repoName.txt'); + console.log("Cleaned up repoName.txt"); } }); const url = randomUrl(); -test.describe.serial("Combined user tests", () => { - test("Login as user 1 (admin)", { tag: "@admin" }, async ({ page }) => { - await test.step('Navigate to the repository page', async () => { - await switchToUser(page, process.env.USER1USERNAME!); - console.log('\n Try to delete old repos\n'); +test.describe("Combined user tests", () => { + test("Login as user 1 (admin)", async ({ page }) => { + await test.step("Navigate to the repository page", async () => { + 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 switchToUser(page, process.env.RO_USER_USERNAME!); - 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 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(); - }); - }); -}); \ No newline at end of file + 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 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(); + }); + } + ); +}); diff --git a/tests/auth.setup.ts b/tests/auth.setup.ts index b75cf7c..7df10fc 100644 --- a/tests/auth.setup.ts +++ b/tests/auth.setup.ts @@ -4,14 +4,10 @@ import { closePopupsIfExist, switchToUser, logInWithUsernameAndPassword, - ensureNotInPreview, logout, } from "./helpers/loginHelpers"; import { describe } from "node:test"; -const authFile = '.auth/contentPlaywrightUserAdmin.json'; -const authFileRO = '.auth/contentPlaywrightReader.json'; - describe("Setup", async () => { setup("Ensure needed ENV variables exist", async ({}) => { expect(() => throwIfMissingEnvVariables()).not.toThrow(); @@ -19,31 +15,23 @@ describe("Setup", async () => { setup("Authenticate all the users", async ({ page }) => { await closePopupsIfExist(page); + await logInWithUsernameAndPassword( page, - process.env.USER1USERNAME, - process.env.USER1PASSWORD + process.env.READONLY_USERNAME, + process.env.READONLY_PASSWORD ); - await page.context().storageState({ path: authFile }); await logout(page); + await logInWithUsernameAndPassword( page, - process.env.RO_USER_USERNAME, - process.env.RO_USER_PASSWORD + process.env.ADMIN_USERNAME, + process.env.ADMIN_PASSWORD ); - await page.context().storageState({ path: authFileRO }); - 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 switchToUser(page, process.env.ADMIN_USERNAME!); + + // We do this as we run admin tests first. }); }); diff --git a/tests/helpers/loginHelpers.ts b/tests/helpers/loginHelpers.ts index 403401e..986ed9f 100644 --- a/tests/helpers/loginHelpers.ts +++ b/tests/helpers/loginHelpers.ts @@ -1,5 +1,6 @@ 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 @@ -19,6 +20,44 @@ export const logout = async (page: Page) => { }).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, @@ -46,21 +85,12 @@ export const logInWithUsernameAndPassword = async ( await passwordField.fill(password); await passwordField.press("Enter"); - await expect(async () => { - expect(page.url()).toBe( - `${process.env.BASE_URL}/insights/content/repositories` - ); - }).toPass(); -}; -export const switchToUser = async (page: Page, userName: string) => { - const { cookies } = await page.context().storageState({ - path: path.join(__dirname, `../../.auth/${userName}.json`), - }); - process.env.TOKEN = `Bearer ${ - cookies.find((cookie) => cookie.name === "cs_jwt")?.value - }`; - await page.waitForTimeout(100); + await expect( + page.getByRole("heading", { name: "Repositories", exact: false }) + ).toBeVisible(); + + await storeUserAuth(page, username); }; export const closePopupsIfExist = async (page: Page) => { @@ -81,8 +111,8 @@ export const closePopupsIfExist = async (page: Page) => { export const throwIfMissingEnvVariables = () => { const ManditoryEnvVariables = [ - "USER1USERNAME", - "USER1PASSWORD", + "ADMIN_USERNAME", + "ADMIN_PASSWORD", "BASE_URL", "ORG_ID_1", "ACTIVATION_KEY_1", diff --git a/tests/switchToUser2.setup.ts b/tests/switchToUser2.setup.ts new file mode 100644 index 0000000..52a6047 --- /dev/null +++ b/tests/switchToUser2.setup.ts @@ -0,0 +1,10 @@ +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 96495e69c4acac78d9c08b4cc0e2d3b89725d25a Mon Sep 17 00:00:00 2001 From: swadeley Date: Fri, 2 May 2025 15:01:13 +0100 Subject: [PATCH 09/14] fixuptest --- tests/Integration/twouserRBACTest.spec.ts | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/tests/Integration/twouserRBACTest.spec.ts b/tests/Integration/twouserRBACTest.spec.ts index e861fa0..e9cb53d 100644 --- a/tests/Integration/twouserRBACTest.spec.ts +++ b/tests/Integration/twouserRBACTest.spec.ts @@ -22,19 +22,18 @@ const getRepoName = (): string => { return repoName; }; -// Clean up the repo name file -test.beforeAll(async () => { - if (fs.existsSync(repoNameFile)) { - fs.unlinkSync(repoNameFile); - console.log("Cleaned up repoName.txt"); - } -}); + 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); @@ -99,15 +98,7 @@ test.describe("Combined user tests", () => { const row = await getRowByNameOrUrl(page, `${repoName}-Edited`); await expect(row.getByText("Valid")).toBeVisible({ timeout: 60000 }); await row.getByLabel("Kebab toggle").click(); - 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 expect(row.getByRole("menuitem", { name: "Edit" })).not.toBeVisible(); }); } ); From cfe628e843c0d9f3a9d695d4c4865f01da5c9a9e Mon Sep 17 00:00:00 2001 From: swadeley Date: Wed, 7 May 2025 15:09:28 +0100 Subject: [PATCH 10/14] ignore repoName.txt --- .gitignore | 3 ++- repoName.txt | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) delete mode 100644 repoName.txt diff --git a/.gitignore b/.gitignore index 694302a..f896950 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,5 @@ node_modules/ .DS_Store .env .auth -bin \ No newline at end of file +bin +repoName.txt diff --git a/repoName.txt b/repoName.txt deleted file mode 100644 index 9068369..0000000 --- a/repoName.txt +++ /dev/null @@ -1 +0,0 @@ -Repo-RBAC-hms2 \ No newline at end of file From 9fa656eb534b83752711ca9333397561a6e30616 Mon Sep 17 00:00:00 2001 From: swadeley Date: Fri, 16 May 2025 21:34:48 +0800 Subject: [PATCH 11/14] typo in config.ts --- playwright.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playwright.config.ts b/playwright.config.ts index ccce150..4681256 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -58,7 +58,7 @@ export default defineConfig({ grepInvert: [/read-only/], // !!process.env.PROD ? [/preview-only/, /switch-to-preview/], ] : [/switch-to-preview/], use: { ...devices["Desktop Chrome"], - storageState: `./.auth/${process.env.ADMIN_USERNAME}.json`, // Thise is setting the cookies + storageState: `./.auth/${process.env.ADMIN_USERNAME}.json`, // This is setting the cookies }, dependencies: ["setup"], }, From a79972064a10c954fbee3d2336a0c64df83c4455 Mon Sep 17 00:00:00 2001 From: swadeley Date: Wed, 21 May 2025 11:18:31 +0800 Subject: [PATCH 12/14] zerostate handling --- tests/helpers/loginHelpers.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/helpers/loginHelpers.ts b/tests/helpers/loginHelpers.ts index 986ed9f..8d347b6 100644 --- a/tests/helpers/loginHelpers.ts +++ b/tests/helpers/loginHelpers.ts @@ -87,8 +87,9 @@ export const logInWithUsernameAndPassword = async ( await expect( - page.getByRole("heading", { name: "Repositories", exact: false }) - ).toBeVisible(); + page.getByText('View all repositories within your organization.') + .or(page.getByText('Add repositories now', { exact: true })) + ).toBeVisible(); await storeUserAuth(page, username); }; From ee9fb8e537ad8aeefe55ef3ae98f3c94b3023443 Mon Sep 17 00:00:00 2001 From: swadeley Date: Wed, 21 May 2025 10:16:54 +0800 Subject: [PATCH 13/14] const authDir --- tests/auth.setup.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/auth.setup.ts b/tests/auth.setup.ts index 7df10fc..32055eb 100644 --- a/tests/auth.setup.ts +++ b/tests/auth.setup.ts @@ -8,6 +8,12 @@ import { } from "./helpers/loginHelpers"; import { describe } from "node:test"; +import { existsSync, mkdirSync } from 'fs'; +const authDir = '.auth'; +if (!existsSync(authDir)) { + mkdirSync(authDir); +} + describe("Setup", async () => { setup("Ensure needed ENV variables exist", async ({}) => { expect(() => throwIfMissingEnvVariables()).not.toThrow(); From 36e72a91a6b9d150e803b94377101fe8ee1db722 Mon Sep 17 00:00:00 2001 From: swadeley Date: Wed, 21 May 2025 11:50:07 +0800 Subject: [PATCH 14/14] patternfly version agnostic --- tests/helpers/loginHelpers.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/helpers/loginHelpers.ts b/tests/helpers/loginHelpers.ts index 8d347b6..bf0239f 100644 --- a/tests/helpers/loginHelpers.ts +++ b/tests/helpers/loginHelpers.ts @@ -96,8 +96,8 @@ export const logInWithUsernameAndPassword = async ( export const closePopupsIfExist = async (page: Page) => { const locatorsToCheck = [ - page.locator(".pf-v6-c-modal-box__close > button"), - page.locator(".pf-v5-c-alert.notification-item button"), // This closes all toast pop-ups + 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.)