Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,4 @@ playwright/.cache/
.env
.auth
bin
repoName.txt
81 changes: 81 additions & 0 deletions _playwright-tests/UI/TwoUserRBAC.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
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';

// 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;
};

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);
}
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 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();
});
});
});
4 changes: 2 additions & 2 deletions _playwright-tests/UI/helpers/navHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Expand Down Expand Up @@ -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();
Expand Down
38 changes: 26 additions & 12 deletions _playwright-tests/auth.setup.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,37 @@
import { expect, test as setup } from '@playwright/test';
import { expect, test as setup, type Page } from '@playwright/test';

Check failure on line 1 in _playwright-tests/auth.setup.ts

View workflow job for this annotation

GitHub Actions / lint

'Page' is defined but never used

Check failure on line 1 in _playwright-tests/auth.setup.ts

View workflow job for this annotation

GitHub Actions / lint

'Page' is defined but never used
import {
throwIfMissingEnvVariables,
logInWithUser1,
storeStorageStateAndToken,
logInWithUsernameAndPassword,
logout,
switchToUser,
} 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 ({}) => {

Check failure on line 12 in _playwright-tests/auth.setup.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected empty object pattern
expect(() => throwIfMissingEnvVariables()).not.toThrow();
});

setup('Authenticate', async ({ page }) => {
setup.setTimeout(60_000);

setup('Authenticate all the users', async ({ page }) => {
await closePopupsIfExist(page);
await logInWithUser1(page);
await storeStorageStateAndToken(page);

await logInWithUsernameAndPassword(
page,
process.env.READONLY_USERNAME,
process.env.READONLY_PASSWORD,
);

await logout(page);

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.
});
});
152 changes: 152 additions & 0 deletions _playwright-tests/helpers/loginHelpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
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();
};
79 changes: 0 additions & 79 deletions _playwright-tests/helpers/loginHelpers.tsx

This file was deleted.

Loading
Loading