mirror of
https://github.com/hyperdxio/hyperdx
synced 2026-04-21 13:37:15 +00:00
chore: add tests for team page (#1848)
Adds playwright tests for the team page
This commit is contained in:
parent
6d7327aba0
commit
20561c703a
5 changed files with 582 additions and 10 deletions
|
|
@ -53,7 +53,7 @@ function ConnectionsSection() {
|
|||
const [isCreatingConnection, setIsCreatingConnection] = useState(false);
|
||||
|
||||
return (
|
||||
<Box id="connections">
|
||||
<Box id="connections" data-testid="connections-section">
|
||||
<Text size="md">Connections</Text>
|
||||
<Divider my="md" />
|
||||
<Card>
|
||||
|
|
@ -112,6 +112,7 @@ function ConnectionsSection() {
|
|||
{!isCreatingConnection &&
|
||||
(IS_LOCAL_MODE ? (connections?.length ?? 0) < 1 : true) && (
|
||||
<Button
|
||||
data-testid="add-connection-button"
|
||||
variant="primary"
|
||||
onClick={() => setIsCreatingConnection(true)}
|
||||
>
|
||||
|
|
@ -142,7 +143,7 @@ function ConnectionsSection() {
|
|||
|
||||
function SourcesSection() {
|
||||
return (
|
||||
<Box id="sources">
|
||||
<Box id="sources" data-testid="sources-section">
|
||||
<Text size="md">Sources</Text>
|
||||
<Divider my="md" />
|
||||
<SourcesList
|
||||
|
|
@ -155,7 +156,7 @@ function SourcesSection() {
|
|||
}
|
||||
function IntegrationsSection() {
|
||||
return (
|
||||
<Box id="integrations">
|
||||
<Box id="integrations" data-testid="integrations-section">
|
||||
<Text size="md">Integrations</Text>
|
||||
<Divider my="md" />
|
||||
<Card>
|
||||
|
|
@ -203,7 +204,7 @@ function TeamNameSection() {
|
|||
[refetchTeam, setTeamName],
|
||||
);
|
||||
return (
|
||||
<Box id="team_name">
|
||||
<Box id="team_name" data-testid="team-name-section">
|
||||
<Text size="md">Team Name</Text>
|
||||
<Divider my="md" />
|
||||
<Card>
|
||||
|
|
@ -211,6 +212,7 @@ function TeamNameSection() {
|
|||
<form onSubmit={form.handleSubmit(onSubmit)}>
|
||||
<Group gap="xs">
|
||||
<TextInput
|
||||
data-testid="team-name-input"
|
||||
size="xs"
|
||||
placeholder="My Team"
|
||||
required
|
||||
|
|
@ -227,6 +229,7 @@ function TeamNameSection() {
|
|||
}}
|
||||
/>
|
||||
<Button
|
||||
data-testid="team-name-save-button"
|
||||
type="submit"
|
||||
size="xs"
|
||||
variant="primary"
|
||||
|
|
@ -235,6 +238,7 @@ function TeamNameSection() {
|
|||
Save
|
||||
</Button>
|
||||
<Button
|
||||
data-testid="team-name-cancel-button"
|
||||
type="button"
|
||||
size="xs"
|
||||
variant="secondary"
|
||||
|
|
@ -247,9 +251,12 @@ function TeamNameSection() {
|
|||
</form>
|
||||
) : (
|
||||
<Group gap="lg">
|
||||
<div className="fs-7">{team?.name}</div>
|
||||
<div className="fs-7" data-testid="team-name-display">
|
||||
{team?.name}
|
||||
</div>
|
||||
{hasAdminAccess && (
|
||||
<Button
|
||||
data-testid="team-name-change-button"
|
||||
size="xs"
|
||||
variant="secondary"
|
||||
leftSection={<IconPencil size={16} />}
|
||||
|
|
@ -582,7 +589,7 @@ function ApiKeysSection() {
|
|||
};
|
||||
|
||||
return (
|
||||
<Box id="api_keys">
|
||||
<Box id="api_keys" data-testid="api-keys-section">
|
||||
<Text size="md">API Keys</Text>
|
||||
<Divider my="md" />
|
||||
<Card mb="md">
|
||||
|
|
@ -593,6 +600,7 @@ function ApiKeysSection() {
|
|||
)}
|
||||
{hasAdminAccess && (
|
||||
<Button
|
||||
data-testid="rotate-api-key-button"
|
||||
variant="danger"
|
||||
onClick={() => setRotateApiKeyConfirmationModalShow(true)}
|
||||
>
|
||||
|
|
@ -619,6 +627,7 @@ function ApiKeysSection() {
|
|||
</Text>
|
||||
<Group justify="end">
|
||||
<Button
|
||||
data-testid="rotate-api-key-cancel"
|
||||
variant="secondary"
|
||||
className="mt-2 px-4 ms-2 float-end"
|
||||
size="sm"
|
||||
|
|
@ -627,6 +636,7 @@ function ApiKeysSection() {
|
|||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
data-testid="rotate-api-key-confirm"
|
||||
variant="danger"
|
||||
className="mt-2 px-4 float-end"
|
||||
size="sm"
|
||||
|
|
@ -657,7 +667,7 @@ export default function TeamPage() {
|
|||
team?.allowedAuthMethods != null && team?.allowedAuthMethods.length > 0;
|
||||
|
||||
return (
|
||||
<div className="TeamPage">
|
||||
<div className="TeamPage" data-testid="team-page">
|
||||
<Head>
|
||||
<title>My Team - {brandName}</title>
|
||||
</Head>
|
||||
|
|
|
|||
|
|
@ -205,7 +205,7 @@ export default function TeamMembersSection() {
|
|||
};
|
||||
|
||||
return (
|
||||
<Box id="team_members">
|
||||
<Box id="team_members" data-testid="team-members-section">
|
||||
<Text size="md">Team</Text>
|
||||
<Divider my="md" />
|
||||
|
||||
|
|
@ -214,6 +214,7 @@ export default function TeamMembersSection() {
|
|||
<Group align="center" justify="space-between">
|
||||
<div className="fs-7">Team Members</div>
|
||||
<Button
|
||||
data-testid="invite-member-button"
|
||||
variant="primary"
|
||||
leftSection={<IconUserPlus size={16} />}
|
||||
onClick={() => setTeamInviteModalShow(true)}
|
||||
|
|
@ -361,6 +362,7 @@ export default function TeamMembersSection() {
|
|||
</Text>
|
||||
<Group justify="flex-end" gap="xs">
|
||||
<Button
|
||||
data-testid="cancel-delete-member"
|
||||
variant="secondary"
|
||||
onClick={() =>
|
||||
setDeleteTeamMemberConfirmationModalData({
|
||||
|
|
@ -373,6 +375,7 @@ export default function TeamMembersSection() {
|
|||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
data-testid="confirm-delete-member"
|
||||
variant="danger"
|
||||
onClick={() =>
|
||||
deleteTeamMemberConfirmationModalData.id &&
|
||||
|
|
@ -408,6 +411,7 @@ function InviteTeamMemberForm({
|
|||
>
|
||||
<Stack>
|
||||
<TextInput
|
||||
data-testid="invite-email-input"
|
||||
label="Email"
|
||||
name="email"
|
||||
type="email"
|
||||
|
|
@ -421,6 +425,7 @@ function InviteTeamMemberForm({
|
|||
The invite link will automatically expire after 30 days.
|
||||
</div>
|
||||
<Button
|
||||
data-testid="send-invite-button"
|
||||
variant="primary"
|
||||
type="submit"
|
||||
disabled={!email || isSubmitting}
|
||||
|
|
|
|||
|
|
@ -104,7 +104,13 @@ export default function WebhooksSection() {
|
|||
|
||||
<Stack>
|
||||
{groupedWebhooks.length === 0 ? (
|
||||
<Text size="sm" c="dimmed" ta="center" py="xl">
|
||||
<Text
|
||||
data-testid="webhooks-empty-state"
|
||||
size="sm"
|
||||
c="dimmed"
|
||||
ta="center"
|
||||
py="xl"
|
||||
>
|
||||
No webhooks configured yet
|
||||
</Text>
|
||||
) : (
|
||||
|
|
@ -190,7 +196,11 @@ export default function WebhooksSection() {
|
|||
</Stack>
|
||||
|
||||
{!isAddWebhookModalOpen ? (
|
||||
<Button variant="secondary" onClick={openWebhookModal}>
|
||||
<Button
|
||||
data-testid="add-webhook-section-button"
|
||||
variant="secondary"
|
||||
onClick={openWebhookModal}
|
||||
>
|
||||
Add Webhook
|
||||
</Button>
|
||||
) : (
|
||||
|
|
|
|||
224
packages/app/tests/e2e/features/team.spec.ts
Normal file
224
packages/app/tests/e2e/features/team.spec.ts
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
// seed: packages/app/tests/e2e/features/dashboard.spec.ts
|
||||
|
||||
import { TeamPage } from '../page-objects/TeamPage';
|
||||
import { expect, test } from '../utils/base-test';
|
||||
|
||||
test.describe('Team Settings Page', { tag: ['@team', '@full-stack'] }, () => {
|
||||
let teamPage: TeamPage;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
teamPage = new TeamPage(page);
|
||||
await teamPage.goto();
|
||||
});
|
||||
|
||||
test('should load team page with all sections visible', async () => {
|
||||
await test.step('Verify page container is visible', async () => {
|
||||
await expect(teamPage.container).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('Verify all major sections are visible', async () => {
|
||||
await expect(teamPage.sources).toBeVisible();
|
||||
await expect(teamPage.connections).toBeVisible();
|
||||
await expect(teamPage.integrations).toBeVisible();
|
||||
await expect(teamPage.teamName).toBeVisible();
|
||||
await expect(teamPage.apiKeys).toBeVisible();
|
||||
await expect(teamPage.members).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('Verify section headings exist', async () => {
|
||||
await expect(
|
||||
teamPage.sources.getByText('Sources', { exact: true }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
teamPage.connections.getByText('Connections', { exact: true }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
teamPage.integrations.getByText('Integrations', { exact: true }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
teamPage.teamName.getByText('Team Name', { exact: true }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
teamPage.apiKeys.getByText('API Keys', { exact: true }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
teamPage.members.getByText('Team', { exact: true }),
|
||||
).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test('should change team name', async () => {
|
||||
test.setTimeout(90000);
|
||||
let originalName: string;
|
||||
|
||||
await test.step('Read current team name', async () => {
|
||||
const text = await teamPage.getTeamNameText();
|
||||
originalName = (text ?? '').trim();
|
||||
});
|
||||
|
||||
await test.step('Start editing and verify edit controls', async () => {
|
||||
await teamPage.startEditingTeamName();
|
||||
await expect(teamPage.teamNameSave).toBeVisible();
|
||||
await expect(teamPage.teamNameCancel).toBeVisible();
|
||||
});
|
||||
|
||||
const newName = `E2E Team ${Date.now()}`;
|
||||
|
||||
await test.step('Fill and save new team name', async () => {
|
||||
await teamPage.fillTeamName(newName);
|
||||
await teamPage.saveTeamName();
|
||||
});
|
||||
|
||||
await test.step('Verify success notification and new name', async () => {
|
||||
await expect(teamPage.page.getByText('Updated team name')).toBeVisible();
|
||||
await expect(teamPage.teamNameValue).toHaveText(newName);
|
||||
});
|
||||
|
||||
await test.step('Restore original team name', async () => {
|
||||
await teamPage.changeTeamName(originalName!);
|
||||
await expect(teamPage.page.getByText('Updated team name')).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test('should cancel team name editing', async () => {
|
||||
let originalName: string;
|
||||
|
||||
await test.step('Read current team name', async () => {
|
||||
const text = await teamPage.getTeamNameText();
|
||||
originalName = (text ?? '').trim();
|
||||
});
|
||||
|
||||
await test.step('Start editing and fill new name', async () => {
|
||||
await teamPage.startEditingTeamName();
|
||||
await teamPage.fillTeamName(`E2E Temp ${Date.now()}`);
|
||||
});
|
||||
|
||||
await test.step('Cancel editing', async () => {
|
||||
await teamPage.cancelEditingTeamName();
|
||||
});
|
||||
|
||||
await test.step('Verify original name is still displayed', async () => {
|
||||
await expect(teamPage.teamNameValue).toHaveText(originalName!);
|
||||
await expect(teamPage.teamNameSave).toBeHidden();
|
||||
});
|
||||
});
|
||||
|
||||
test('should display API keys', async () => {
|
||||
await test.step('Scroll to API keys section', async () => {
|
||||
await teamPage.apiKeys.scrollIntoViewIfNeeded();
|
||||
});
|
||||
|
||||
await test.step('Verify API key labels are visible', async () => {
|
||||
await expect(
|
||||
teamPage.apiKeys.getByText('Ingestion API Key'),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
teamPage.apiKeys.getByText('Personal API Access Key'),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('Verify rotate button is visible', async () => {
|
||||
await expect(teamPage.rotateButton).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test('should open and cancel rotate API key modal', async () => {
|
||||
await test.step('Open rotate API key modal', async () => {
|
||||
await teamPage.clickRotateApiKey();
|
||||
});
|
||||
|
||||
await test.step('Verify modal shows irreversible warning', async () => {
|
||||
await expect(teamPage.page.getByText('not reversible')).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('Cancel and verify modal closes', async () => {
|
||||
await teamPage.cancelRotateApiKey();
|
||||
await expect(teamPage.page.getByText('not reversible')).toBeHidden();
|
||||
});
|
||||
});
|
||||
|
||||
test('should create and delete a webhook', async () => {
|
||||
test.setTimeout(90000);
|
||||
const ts = Date.now();
|
||||
const webhookName = `E2E Webhook ${ts}`;
|
||||
const webhookUrl = `https://example.com/e2e-webhook-${ts}`;
|
||||
|
||||
await test.step('Scroll to integrations and create webhook', async () => {
|
||||
await teamPage.integrations.scrollIntoViewIfNeeded();
|
||||
await teamPage.createWebhook({
|
||||
serviceType: 'Generic',
|
||||
name: webhookName,
|
||||
url: webhookUrl,
|
||||
});
|
||||
});
|
||||
|
||||
await test.step('Verify webhook created successfully', async () => {
|
||||
await expect(
|
||||
teamPage.page.getByText('Webhook created successfully'),
|
||||
).toBeVisible();
|
||||
await expect(teamPage.integrations.getByText(webhookName)).toBeVisible();
|
||||
await expect(teamPage.integrations.getByText(webhookUrl)).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('Delete the webhook', async () => {
|
||||
await teamPage.deleteWebhookByName(webhookName);
|
||||
await teamPage.confirmDialog();
|
||||
});
|
||||
|
||||
await test.step('Verify webhook deleted successfully', async () => {
|
||||
await expect(
|
||||
teamPage.page.getByText('Webhook deleted successfully'),
|
||||
).toBeVisible();
|
||||
await expect(teamPage.integrations.getByText(webhookName)).toBeHidden();
|
||||
});
|
||||
});
|
||||
|
||||
test('should invite a team member and delete the invitation', async () => {
|
||||
test.setTimeout(90000);
|
||||
const email = `e2e-test-${Date.now()}@example.com`;
|
||||
|
||||
await test.step('Verify current user is displayed', async () => {
|
||||
await teamPage.members.scrollIntoViewIfNeeded();
|
||||
await expect(teamPage.members.getByText('You')).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('Open invite modal and send invitation', async () => {
|
||||
await teamPage.clickInviteMember();
|
||||
await expect(
|
||||
teamPage.page.getByRole('dialog').getByText('Invite Team Member'),
|
||||
).toBeVisible();
|
||||
await teamPage.fillInviteEmail(email);
|
||||
await teamPage.submitInvite();
|
||||
});
|
||||
|
||||
await test.step('Verify invitation appears with pending badge', async () => {
|
||||
const row = teamPage.getInvitationRow(email);
|
||||
await expect(row).toBeVisible({ timeout: 10000 });
|
||||
await expect(row.getByText(email)).toBeVisible();
|
||||
await expect(row.getByText('Pending Invite')).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('Delete the invitation', async () => {
|
||||
await teamPage.deleteInvitationByEmail(email);
|
||||
await teamPage.confirmDeleteMember();
|
||||
});
|
||||
|
||||
await test.step('Verify invitation deleted successfully', async () => {
|
||||
await expect(
|
||||
teamPage.page.getByText('Deleted team invite'),
|
||||
).toBeVisible();
|
||||
await expect(teamPage.members.getByText(email)).toBeHidden();
|
||||
});
|
||||
});
|
||||
|
||||
test('should display connection information', async () => {
|
||||
await test.step('Verify connections section is visible', async () => {
|
||||
await expect(teamPage.connections).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('Verify connection details are visible', async () => {
|
||||
await expect(teamPage.connections.getByText('Host:')).toBeVisible();
|
||||
await expect(teamPage.connections.getByText('Username:')).toBeVisible();
|
||||
});
|
||||
});
|
||||
});
|
||||
323
packages/app/tests/e2e/page-objects/TeamPage.ts
Normal file
323
packages/app/tests/e2e/page-objects/TeamPage.ts
Normal file
|
|
@ -0,0 +1,323 @@
|
|||
/**
|
||||
* TeamPage - Page object for the /team settings page
|
||||
* Encapsulates all interactions with team settings sections:
|
||||
* Sources, Connections, Integrations/Webhooks, Team Name,
|
||||
* ClickHouse Settings, API Keys, and Team Members.
|
||||
*/
|
||||
import { Locator, Page } from '@playwright/test';
|
||||
|
||||
export class TeamPage {
|
||||
readonly page: Page;
|
||||
|
||||
private readonly pageContainer: Locator;
|
||||
|
||||
// Section containers
|
||||
private readonly sourcesSection: Locator;
|
||||
private readonly connectionsSection: Locator;
|
||||
private readonly integrationsSection: Locator;
|
||||
private readonly teamNameSection: Locator;
|
||||
private readonly apiKeysSection: Locator;
|
||||
private readonly teamMembersSection: Locator;
|
||||
|
||||
// Team name elements
|
||||
private readonly teamNameDisplay: Locator;
|
||||
private readonly teamNameChangeButton: Locator;
|
||||
private readonly teamNameInput: Locator;
|
||||
private readonly teamNameSaveButton: Locator;
|
||||
private readonly teamNameCancelButton: Locator;
|
||||
|
||||
// API Keys elements
|
||||
private readonly rotateApiKeyButton: Locator;
|
||||
private readonly rotateApiKeyConfirm: Locator;
|
||||
private readonly rotateApiKeyCancel: Locator;
|
||||
|
||||
// Connections elements
|
||||
private readonly addConnectionButton: Locator;
|
||||
|
||||
// Webhooks elements
|
||||
private readonly addWebhookButton: Locator;
|
||||
private readonly webhooksEmptyState: Locator;
|
||||
|
||||
// Team members elements
|
||||
private readonly inviteMemberButton: Locator;
|
||||
private readonly inviteEmailInput: Locator;
|
||||
private readonly sendInviteButton: Locator;
|
||||
private readonly confirmDeleteMemberButton: Locator;
|
||||
private readonly cancelDeleteMemberButton: Locator;
|
||||
private readonly confirmDialogConfirmBtn: Locator;
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page;
|
||||
this.pageContainer = page.getByTestId('team-page');
|
||||
|
||||
this.sourcesSection = page.getByTestId('sources-section');
|
||||
this.connectionsSection = page.getByTestId('connections-section');
|
||||
this.integrationsSection = page.getByTestId('integrations-section');
|
||||
this.teamNameSection = page.getByTestId('team-name-section');
|
||||
this.apiKeysSection = page.getByTestId('api-keys-section');
|
||||
this.teamMembersSection = page.getByTestId('team-members-section');
|
||||
|
||||
this.teamNameDisplay = page.getByTestId('team-name-display');
|
||||
this.teamNameChangeButton = page.getByTestId('team-name-change-button');
|
||||
this.teamNameInput = page.getByTestId('team-name-input');
|
||||
this.teamNameSaveButton = page.getByTestId('team-name-save-button');
|
||||
this.teamNameCancelButton = page.getByTestId('team-name-cancel-button');
|
||||
|
||||
this.rotateApiKeyButton = page.getByTestId('rotate-api-key-button');
|
||||
this.rotateApiKeyConfirm = page.getByTestId('rotate-api-key-confirm');
|
||||
this.rotateApiKeyCancel = page.getByTestId('rotate-api-key-cancel');
|
||||
|
||||
this.addConnectionButton = page.getByTestId('add-connection-button');
|
||||
|
||||
this.addWebhookButton = page.getByTestId('add-webhook-section-button');
|
||||
this.webhooksEmptyState = page.getByTestId('webhooks-empty-state');
|
||||
|
||||
this.inviteMemberButton = page.getByTestId('invite-member-button');
|
||||
this.inviteEmailInput = page.getByTestId('invite-email-input');
|
||||
this.sendInviteButton = page.getByTestId('send-invite-button');
|
||||
this.confirmDeleteMemberButton = page.getByTestId('confirm-delete-member');
|
||||
this.cancelDeleteMemberButton = page.getByTestId('cancel-delete-member');
|
||||
this.confirmDialogConfirmBtn = page.getByTestId('confirm-confirm-button');
|
||||
}
|
||||
|
||||
async goto() {
|
||||
await this.page.goto('/team');
|
||||
await this.pageContainer.waitFor({ state: 'visible', timeout: 10000 });
|
||||
}
|
||||
|
||||
// --- Team Name ---
|
||||
|
||||
async getTeamNameText() {
|
||||
return this.teamNameDisplay.textContent();
|
||||
}
|
||||
|
||||
async startEditingTeamName() {
|
||||
await this.teamNameChangeButton.click();
|
||||
await this.teamNameInput.waitFor({ state: 'visible' });
|
||||
}
|
||||
|
||||
async fillTeamName(name: string) {
|
||||
await this.teamNameInput.clear();
|
||||
await this.teamNameInput.fill(name);
|
||||
}
|
||||
|
||||
async saveTeamName() {
|
||||
await this.teamNameSaveButton.click();
|
||||
}
|
||||
|
||||
async cancelEditingTeamName() {
|
||||
await this.teamNameCancelButton.click();
|
||||
}
|
||||
|
||||
async changeTeamName(name: string) {
|
||||
await this.startEditingTeamName();
|
||||
await this.fillTeamName(name);
|
||||
await this.saveTeamName();
|
||||
}
|
||||
|
||||
// --- API Keys ---
|
||||
|
||||
async clickRotateApiKey() {
|
||||
await this.rotateApiKeyButton.click();
|
||||
}
|
||||
|
||||
async confirmRotateApiKey() {
|
||||
await this.rotateApiKeyConfirm.click();
|
||||
}
|
||||
|
||||
async cancelRotateApiKey() {
|
||||
await this.rotateApiKeyCancel.click();
|
||||
}
|
||||
|
||||
// --- Connections ---
|
||||
|
||||
async clickAddConnection() {
|
||||
await this.addConnectionButton.click();
|
||||
}
|
||||
|
||||
async fillConnectionForm(opts: {
|
||||
name: string;
|
||||
host: string;
|
||||
username: string;
|
||||
password: string;
|
||||
}) {
|
||||
await this.page.getByTestId('connection-name-input').clear();
|
||||
await this.page.getByTestId('connection-name-input').fill(opts.name);
|
||||
await this.page.getByTestId('connection-host-input').clear();
|
||||
await this.page.getByTestId('connection-host-input').fill(opts.host);
|
||||
await this.page.getByTestId('connection-username-input').clear();
|
||||
await this.page
|
||||
.getByTestId('connection-username-input')
|
||||
.fill(opts.username);
|
||||
await this.page.getByTestId('update-password-button').click();
|
||||
await this.page
|
||||
.getByTestId('connection-password-input')
|
||||
.fill(opts.password);
|
||||
}
|
||||
|
||||
async saveConnection() {
|
||||
await this.page.getByTestId('connection-save-button').click();
|
||||
}
|
||||
|
||||
// --- Webhooks ---
|
||||
|
||||
async clickAddWebhook() {
|
||||
await this.addWebhookButton.click();
|
||||
}
|
||||
|
||||
async fillWebhookForm(opts: {
|
||||
serviceType: 'Slack' | 'incident.io' | 'Generic';
|
||||
name: string;
|
||||
url: string;
|
||||
}) {
|
||||
await this.page
|
||||
.getByTestId('service-type-radio-group')
|
||||
.getByRole('radio', { name: opts.serviceType, exact: true })
|
||||
.click();
|
||||
await this.page.getByTestId('webhook-name-input').fill(opts.name);
|
||||
await this.page.getByTestId('webhook-url-input').fill(opts.url);
|
||||
}
|
||||
|
||||
async submitWebhookForm() {
|
||||
await this.page.getByTestId('add-webhook-button').click();
|
||||
}
|
||||
|
||||
async createWebhook(opts: {
|
||||
serviceType: 'Slack' | 'incident.io' | 'Generic';
|
||||
name: string;
|
||||
url: string;
|
||||
}) {
|
||||
await this.clickAddWebhook();
|
||||
await this.fillWebhookForm(opts);
|
||||
await this.submitWebhookForm();
|
||||
}
|
||||
|
||||
// --- Team Members ---
|
||||
|
||||
async clickInviteMember() {
|
||||
await this.inviteMemberButton.click();
|
||||
}
|
||||
|
||||
async fillInviteEmail(email: string) {
|
||||
await this.inviteEmailInput.fill(email);
|
||||
}
|
||||
|
||||
async submitInvite() {
|
||||
await this.sendInviteButton.click();
|
||||
}
|
||||
|
||||
async inviteTeamMember(email: string) {
|
||||
await this.clickInviteMember();
|
||||
await this.fillInviteEmail(email);
|
||||
await this.submitInvite();
|
||||
}
|
||||
|
||||
async confirmDeleteMember() {
|
||||
await this.confirmDeleteMemberButton.click();
|
||||
}
|
||||
|
||||
async cancelDeleteMember() {
|
||||
await this.cancelDeleteMemberButton.click();
|
||||
}
|
||||
|
||||
getInvitationRow(email: string) {
|
||||
return this.teamMembersSection.locator('tr').filter({ hasText: email });
|
||||
}
|
||||
|
||||
async deleteInvitationByEmail(email: string) {
|
||||
await this.getInvitationRow(email)
|
||||
.getByRole('button', { name: 'Delete' })
|
||||
.click();
|
||||
}
|
||||
|
||||
async deleteWebhookByName(webhookName: string) {
|
||||
const webhookItem = this.integrationsSection
|
||||
.locator('div')
|
||||
.filter({ hasText: webhookName })
|
||||
.filter({ has: this.page.getByRole('button', { name: 'Delete' }) })
|
||||
.last();
|
||||
await webhookItem.getByRole('button', { name: 'Delete' }).click();
|
||||
}
|
||||
|
||||
async confirmDialog() {
|
||||
await this.confirmDialogConfirmBtn.click();
|
||||
}
|
||||
|
||||
// --- Getters for assertions ---
|
||||
|
||||
get container() {
|
||||
return this.pageContainer;
|
||||
}
|
||||
|
||||
get sources() {
|
||||
return this.sourcesSection;
|
||||
}
|
||||
|
||||
get connections() {
|
||||
return this.connectionsSection;
|
||||
}
|
||||
|
||||
get integrations() {
|
||||
return this.integrationsSection;
|
||||
}
|
||||
|
||||
get teamName() {
|
||||
return this.teamNameSection;
|
||||
}
|
||||
|
||||
get teamNameValue() {
|
||||
return this.teamNameDisplay;
|
||||
}
|
||||
|
||||
get teamNameEditButton() {
|
||||
return this.teamNameChangeButton;
|
||||
}
|
||||
|
||||
get teamNameSave() {
|
||||
return this.teamNameSaveButton;
|
||||
}
|
||||
|
||||
get teamNameCancel() {
|
||||
return this.teamNameCancelButton;
|
||||
}
|
||||
|
||||
get apiKeys() {
|
||||
return this.apiKeysSection;
|
||||
}
|
||||
|
||||
get rotateButton() {
|
||||
return this.rotateApiKeyButton;
|
||||
}
|
||||
|
||||
get members() {
|
||||
return this.teamMembersSection;
|
||||
}
|
||||
|
||||
get inviteButton() {
|
||||
return this.inviteMemberButton;
|
||||
}
|
||||
|
||||
get webhooksEmpty() {
|
||||
return this.webhooksEmptyState;
|
||||
}
|
||||
|
||||
get addWebhook() {
|
||||
return this.addWebhookButton;
|
||||
}
|
||||
|
||||
get addConnection() {
|
||||
return this.addConnectionButton;
|
||||
}
|
||||
|
||||
get connectionForm() {
|
||||
return this.page.getByTestId('connection-form');
|
||||
}
|
||||
|
||||
get webhookNameInput() {
|
||||
return this.page.getByTestId('webhook-name-input');
|
||||
}
|
||||
|
||||
get webhookUrlInput() {
|
||||
return this.page.getByTestId('webhook-url-input');
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue