Skip to content
Merged
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
7 changes: 6 additions & 1 deletion .github/workflows/deploy-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ jobs:

test-dev:
runs-on: ubuntu-latest
name: Run Live Integration Tests
name: Run Live Tests
needs:
- deploy-dev
concurrency:
Expand All @@ -92,3 +92,8 @@ jobs:
run: make dev_health_check
- name: Run live testing
run: make test_live_integration
- name: Run E2E testing
run: make test_e2e
env:
PLAYWRIGHT_USERNAME: ${{ secrets.PLAYWRIGHT_USERNAME }}
PLAYWRIGHT_PASSWORD: ${{ secrets.PLAYWRIGHT_PASSWORD }}
7 changes: 6 additions & 1 deletion .github/workflows/deploy-prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ jobs:

test-dev:
runs-on: ubuntu-latest
name: Run Live Integration Tests
name: Run Live Tests
needs:
- deploy-dev
concurrency:
Expand All @@ -90,6 +90,11 @@ jobs:
python-version: 3.11
- name: Run live testing
run: make test_live_integration
- name: Run E2E testing
run: make test_e2e
env:
PLAYWRIGHT_USERNAME: ${{ secrets.PLAYWRIGHT_USERNAME }}
PLAYWRIGHT_PASSWORD: ${{ secrets.PLAYWRIGHT_PASSWORD }}

deploy-prod:
runs-on: ubuntu-latest
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,7 @@ dist_ui/

*.pyc
__pycache__
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ test_unit: install_test_deps
yarn prettier
yarn test:unit

test_e2e: install_test_deps
yarn playwright install
yarn test:e2e

dev_health_check:
curl -f https://$(application_key).aws.qa.acmuiuc.org/api/v1/healthz && curl -f https://manage.qa.acmuiuc.org

Expand Down
56 changes: 56 additions & 0 deletions e2e/base.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { test as base } from '@playwright/test';
import {
SecretsManagerClient,
GetSecretValueCommand,
} from "@aws-sdk/client-secrets-manager";

export const getSecretValue = async (
secretId: string,
): Promise<Record<string, string | number | boolean> | null> => {
const smClient = new SecretsManagerClient();
const data = await smClient.send(
new GetSecretValueCommand({ SecretId: secretId }),
);
if (!data.SecretString) {
return null;
}
try {
return JSON.parse(data.SecretString) as Record<
string,
string | number | boolean
>;
} catch {
return null;
}
};

async function getSecrets() {
let response = { PLAYWRIGHT_USERNAME: '', PLAYWRIGHT_PASSWORD: '' }
let keyData;
if (!process.env.PLAYWRIGHT_USERNAME || !process.env.PLAYWRIGHT_PASSWORD) {
keyData = await getSecretValue('infra-core-api-config')
}
response['PLAYWRIGHT_USERNAME'] = process.env.PLAYWRIGHT_USERNAME || (keyData ? keyData['playwright_username'] : '');
response['PLAYWRIGHT_PASSWORD'] = process.env.PLAYWRIGHT_PASSWORD || (keyData ? keyData['playwright_password'] : '');
return response;
}

const secrets = await getSecrets();

async function becomeUser(page) {
await page.goto('https://manage.qa.acmuiuc.org/login');
await page.getByRole('button', { name: 'Sign in with Illinois NetID' }).click();
await page.getByPlaceholder('NetID@illinois.edu').click();
await page.getByPlaceholder('NetID@illinois.edu').fill(secrets['PLAYWRIGHT_USERNAME']);
await page.getByPlaceholder('NetID@illinois.edu').press('Enter');
await page.getByPlaceholder('Password').click();
await page.getByPlaceholder('Password').fill(secrets['PLAYWRIGHT_PASSWORD']);
await page.getByRole('button', { name: 'Sign in' }).click();
await page.getByRole('button', { name: 'No' }).click();
}

export const test = base.extend<{ becomeUser: (page) => Promise<void> }>({
becomeUser: async ({ }, use) => {
use(becomeUser)
},
});
20 changes: 20 additions & 0 deletions e2e/tests/login.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@

import { expect } from '@playwright/test';
import { test } from '../base';
import { describe } from 'node:test';

describe("Login tests", () => {
test('A user can login and view the home screen', async ({ page, becomeUser }) => {
await becomeUser(page);
await expect(page.locator('a').filter({ hasText: 'Management Portal DEV ENV' })).toBeVisible();
await expect(page.locator('a').filter({ hasText: 'Events' })).toBeVisible();
await expect(page.locator('a').filter({ hasText: 'Ticketing/Merch' })).toBeVisible();
await expect(page.locator('a').filter({ hasText: 'IAM' })).toBeVisible();
await expect(page.getByRole('link', { name: 'ACM Logo Management Portal' })).toBeVisible();
await expect(page.getByRole('link', { name: 'P', exact: true })).toBeVisible();
await page.getByRole('link', { name: 'P', exact: true }).click();
await expect(page.getByLabel('PMy Account')).toContainText('Name Playwright Core User');
await expect(page.getByLabel('PMy Account')).toContainText('Emailcore-e2e-testing@acm.illinois.edu');
expect(page.url()).toEqual('https://manage.qa.acmuiuc.org/home');
});
})
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@
"test:unit-ui": "yarn test:unit --ui",
"test:unit-watch": "vitest tests/unit",
"test:live": "vitest tests/live",
"test:live-ui": "yarn test:live --ui"
"test:live-ui": "yarn test:live --ui",
"test:e2e": "playwright test",
"test:e2e-ui": "playwright test --ui"
},
"dependencies": {
"@aws-sdk/client-dynamodb": "^3.624.0",
Expand Down Expand Up @@ -49,6 +51,7 @@
},
"devDependencies": {
"@eslint/compat": "^1.1.1",
"@playwright/test": "^1.49.1",
"@tsconfig/node20": "^20.1.4",
"@types/node": "^22.1.0",
"@types/pluralize": "^0.0.33",
Expand Down
43 changes: 43 additions & 0 deletions playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
testDir: './e2e/tests',
/* Run tests in files in parallel */
fullyParallel: false,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
// baseURL: 'http://127.0.0.1:3000',

/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
},

/* Configure projects for major browsers */
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},

{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
],

/* Run your local dev server before starting the tests */
// webServer: {
// command: 'npm run start',
// url: 'http://127.0.0.1:3000',
// reuseExistingServer: !process.env.CI,
// },
});
1 change: 1 addition & 0 deletions src/common/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ const environmentConfig: EnvironmentConfigType = {
},
UserRoleMapping: {
"infra-unit-test-nogrp@acm.illinois.edu": [AppRoles.TICKETS_SCANNER],
"kLkvWTYwNnJfBkIK7mBi4niXXHYNR7ygbV8utlvFxjw": allAppRoles
},
AzureRoleMapping: { AutonomousWriters: [AppRoles.EVENTS_MANAGER] },
ValidCorsOrigins: [
Expand Down
82 changes: 43 additions & 39 deletions src/ui/pages/iam/GroupMemberManagement.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
import { IconTrash, IconUserPlus } from '@tabler/icons-react';
import { notifications } from '@mantine/notifications';
import { GroupMemberGetResponse, EntraActionResponse } from '@common/types/iam';
import FullScreenLoader from '@ui/components/AuthContext/LoadingScreen';

interface GroupMemberManagementProps {
fetchMembers: () => Promise<GroupMemberGetResponse>;
Expand All @@ -29,7 +30,7 @@ export const GroupMemberManagement: React.FC<GroupMemberManagementProps> = ({
const [toAdd, setToAdd] = useState<string[]>([]);
const [toRemove, setToRemove] = useState<string[]>([]);
const [email, setEmail] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const [confirmationModal, setConfirmationModal] = useState(false);

useEffect(() => {
Expand All @@ -46,6 +47,7 @@ export const GroupMemberManagement: React.FC<GroupMemberManagementProps> = ({
}
};
loadMembers();
setIsLoading(false);
}, [fetchMembers]);

const handleAddMember = () => {
Expand Down Expand Up @@ -132,7 +134,6 @@ export const GroupMemberManagement: React.FC<GroupMemberManagementProps> = ({
setIsLoading(false);
}
};

return (
<Box p="md">
<Text fw={500} mb={4}>
Expand All @@ -145,44 +146,47 @@ export const GroupMemberManagement: React.FC<GroupMemberManagementProps> = ({
Current Members
</Text>
<ScrollArea style={{ height: 250 }}>
<List spacing="sm">
{members.map((member) => (
<ListItem key={member.email}>
<Group justify="space-between">
<Box>
<Text size="sm">
{member.name} ({member.email})
</Text>
{toRemove.includes(member.email) && (
<Badge color="red" size="sm">
Queued for removal
{isLoading && <FullScreenLoader />}
{!isLoading && (
<List spacing="sm">
{members.map((member) => (
<ListItem key={member.email}>
<Group justify="space-between">
<Box>
<Text size="sm">
{member.name} ({member.email})
</Text>
{toRemove.includes(member.email) && (
<Badge color="red" size="sm">
Queued for removal
</Badge>
)}
</Box>
<ActionIcon
color="red"
variant="light"
onClick={() => handleRemoveMember(member.email)}
data-testid={`remove-exec-member-${member.email}`}
>
<IconTrash size={16} />
</ActionIcon>
</Group>
</ListItem>
))}
{toAdd.map((member) => (
<ListItem key={member}>
<Group justify="space-between">
<Box>
<Text size="sm">{member}</Text>
<Badge color="green" size="sm">
Queued for addition
</Badge>
)}
</Box>
<ActionIcon
color="red"
variant="light"
onClick={() => handleRemoveMember(member.email)}
data-testid={`remove-exec-member-${member.email}`}
>
<IconTrash size={16} />
</ActionIcon>
</Group>
</ListItem>
))}
{toAdd.map((member) => (
<ListItem key={member}>
<Group justify="space-between">
<Box>
<Text size="sm">{member}</Text>
<Badge color="green" size="sm">
Queued for addition
</Badge>
</Box>
</Group>
</ListItem>
))}
</List>
</Box>
</Group>
</ListItem>
))}
</List>
)}
</ScrollArea>
</Box>

Expand Down
Loading
Loading