mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-03-16 21:53:07 +00:00
272 lines
10 KiB
TypeScript
272 lines
10 KiB
TypeScript
/**
|
|
* Event Hooks Settings Page Tests
|
|
*
|
|
* Tests for the event hooks settings section, including:
|
|
* - Event hooks management
|
|
* - Ntfy endpoint configuration
|
|
* - Dialog state management (useEffect hook validation)
|
|
*
|
|
* This test also serves as a regression test for the bug where
|
|
* useEffect was not imported in the event-hooks-section.tsx file,
|
|
* causing a runtime error when opening the Ntfy endpoint dialog.
|
|
*/
|
|
|
|
import { test, expect, type Page } from '@playwright/test';
|
|
import { authenticateForTests, navigateToSettings, waitForSuccessToast } from '../utils';
|
|
|
|
// Timeout constants for maintainability
|
|
const TIMEOUTS = {
|
|
sectionVisible: 10000,
|
|
dialogVisible: 5000,
|
|
dialogHidden: 5000,
|
|
endpointVisible: 5000,
|
|
} as const;
|
|
|
|
// Selectors for reuse
|
|
const SELECTORS = {
|
|
eventHooksButton: 'button:has-text("Event Hooks")',
|
|
endpointsTab: 'button[role="tab"]:has-text("Endpoints")',
|
|
sectionText: 'text=Run custom commands or send notifications',
|
|
addEndpointButton: 'button:has-text("Add Endpoint")',
|
|
dialog: '[role="dialog"]',
|
|
dialogTitle: 'text=Add Ntfy Endpoint',
|
|
} as const;
|
|
|
|
/**
|
|
* Navigate to the Event Hooks Endpoints tab
|
|
* This helper reduces code duplication across tests
|
|
*/
|
|
async function navigateToEndpointsTab(page: Page): Promise<void> {
|
|
await navigateToSettings(page);
|
|
|
|
// Click on the Event Hooks section in the navigation
|
|
await page.locator(SELECTORS.eventHooksButton).first().click();
|
|
|
|
// Wait for the event hooks section to be visible
|
|
await expect(page.locator(SELECTORS.sectionText)).toBeVisible({
|
|
timeout: TIMEOUTS.sectionVisible,
|
|
});
|
|
|
|
// Switch to Endpoints tab (ntfy endpoints)
|
|
await page.locator(SELECTORS.endpointsTab).click();
|
|
}
|
|
|
|
test.describe('Event Hooks Settings', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await authenticateForTests(page);
|
|
});
|
|
|
|
test('should load event hooks settings section without errors', async ({ page }) => {
|
|
await navigateToSettings(page);
|
|
|
|
// Click on the Event Hooks section in the navigation
|
|
await page.locator(SELECTORS.eventHooksButton).first().click();
|
|
|
|
// Wait for the event hooks section to be visible
|
|
await expect(page.locator(SELECTORS.sectionText)).toBeVisible({
|
|
timeout: TIMEOUTS.sectionVisible,
|
|
});
|
|
|
|
// Verify the tabs are present
|
|
await expect(page.locator('button[role="tab"]:has-text("Hooks")')).toBeVisible();
|
|
await expect(page.locator(SELECTORS.endpointsTab)).toBeVisible();
|
|
await expect(page.locator('button[role="tab"]:has-text("History")')).toBeVisible();
|
|
});
|
|
|
|
test('should open add ntfy endpoint dialog and verify useEffect resets form', async ({
|
|
page,
|
|
}) => {
|
|
// This test specifically validates that the useEffect hook in NtfyEndpointDialog
|
|
// works correctly - if useEffect was not imported, the form would not reset
|
|
await navigateToEndpointsTab(page);
|
|
|
|
// Click Add Endpoint button
|
|
await page.locator(SELECTORS.addEndpointButton).click();
|
|
|
|
// Dialog should be visible
|
|
const dialog = page.locator(SELECTORS.dialog);
|
|
await expect(dialog).toBeVisible({ timeout: TIMEOUTS.dialogVisible });
|
|
|
|
// Dialog title should indicate adding new endpoint
|
|
await expect(dialog.locator(SELECTORS.dialogTitle)).toBeVisible();
|
|
|
|
// Form should have default values (useEffect reset)
|
|
// This is the critical test - if useEffect was not imported or not working,
|
|
// these assertions would fail because the form state would not be reset
|
|
const nameInput = dialog.locator('input#endpoint-name');
|
|
const serverUrlInput = dialog.locator('input#endpoint-server');
|
|
const topicInput = dialog.locator('input#endpoint-topic');
|
|
|
|
// Name should be empty (reset by useEffect)
|
|
await expect(nameInput).toHaveValue('');
|
|
// Server URL should have default value (reset by useEffect)
|
|
await expect(serverUrlInput).toHaveValue('https://ntfy.sh');
|
|
// Topic should be empty (reset by useEffect)
|
|
await expect(topicInput).toHaveValue('');
|
|
|
|
// Close the dialog
|
|
await page.keyboard.press('Escape');
|
|
await expect(dialog).toBeHidden({ timeout: TIMEOUTS.dialogHidden });
|
|
});
|
|
|
|
test('should open and close endpoint dialog without JavaScript errors', async ({ page }) => {
|
|
// This test verifies the dialog opens without throwing a "useEffect is not defined" error
|
|
// Listen for console errors
|
|
const consoleErrors: string[] = [];
|
|
page.on('console', (msg) => {
|
|
if (msg.type() === 'error') {
|
|
consoleErrors.push(msg.text());
|
|
}
|
|
});
|
|
|
|
await navigateToEndpointsTab(page);
|
|
|
|
// Open and close the dialog multiple times to stress test the useEffect
|
|
for (let i = 0; i < 3; i++) {
|
|
await page.locator(SELECTORS.addEndpointButton).click();
|
|
const dialog = page.locator(SELECTORS.dialog);
|
|
await expect(dialog).toBeVisible({ timeout: TIMEOUTS.dialogVisible });
|
|
await page.keyboard.press('Escape');
|
|
await expect(dialog).toBeHidden({ timeout: TIMEOUTS.dialogHidden });
|
|
}
|
|
|
|
// Verify no React hook related errors occurred
|
|
// This catches "useEffect is not defined", "useState is not defined", etc.
|
|
const reactHookError = consoleErrors.find(
|
|
(error) =>
|
|
(error.includes('useEffect') ||
|
|
error.includes('useState') ||
|
|
error.includes('useCallback')) &&
|
|
error.includes('is not defined')
|
|
);
|
|
expect(reactHookError).toBeUndefined();
|
|
});
|
|
|
|
test('should have enabled toggle working in endpoint dialog', async ({ page }) => {
|
|
await navigateToEndpointsTab(page);
|
|
|
|
// Click Add Endpoint button
|
|
await page.locator(SELECTORS.addEndpointButton).click();
|
|
|
|
const dialog = page.locator(SELECTORS.dialog);
|
|
await expect(dialog).toBeVisible({ timeout: TIMEOUTS.dialogVisible });
|
|
|
|
// Verify the enabled switch exists and is checked by default (useEffect sets enabled=true)
|
|
const enabledSwitch = dialog.locator('#endpoint-enabled');
|
|
await expect(enabledSwitch).toBeChecked();
|
|
|
|
// Click the switch to toggle it off
|
|
await enabledSwitch.click();
|
|
await expect(enabledSwitch).not.toBeChecked();
|
|
|
|
// Click it again to toggle it back on
|
|
await enabledSwitch.click();
|
|
await expect(enabledSwitch).toBeChecked();
|
|
|
|
// Close the dialog
|
|
await page.keyboard.press('Escape');
|
|
});
|
|
|
|
test('should have Add Endpoint button disabled when form is invalid', async ({ page }) => {
|
|
await navigateToEndpointsTab(page);
|
|
|
|
// Click Add Endpoint button
|
|
await page.locator(SELECTORS.addEndpointButton).click();
|
|
|
|
const dialog = page.locator(SELECTORS.dialog);
|
|
await expect(dialog).toBeVisible({ timeout: TIMEOUTS.dialogVisible });
|
|
|
|
// The Add Endpoint button should be disabled because form is empty (name and topic required)
|
|
const addButton = dialog.locator('button:has-text("Add Endpoint")').last();
|
|
await expect(addButton).toBeDisabled();
|
|
|
|
// Fill in name but not topic
|
|
await dialog.locator('input#endpoint-name').fill('Test Name');
|
|
|
|
// Button should still be disabled (topic is required)
|
|
await expect(addButton).toBeDisabled();
|
|
|
|
// Fill in topic with invalid value (contains space)
|
|
await dialog.locator('input#endpoint-topic').fill('invalid topic');
|
|
|
|
// Button should still be disabled (topic has space which is invalid)
|
|
await expect(addButton).toBeDisabled();
|
|
|
|
// Fix the topic
|
|
await dialog.locator('input#endpoint-topic').fill('valid-topic');
|
|
|
|
// Now button should be enabled
|
|
await expect(addButton).toBeEnabled();
|
|
|
|
// Close the dialog
|
|
await page.keyboard.press('Escape');
|
|
});
|
|
|
|
test('should persist ntfy endpoint after adding and page reload', async ({ page }) => {
|
|
// This test verifies that ntfy endpoints are correctly saved to the server
|
|
// and restored when the page is reloaded - the core bug fix being tested
|
|
|
|
await navigateToEndpointsTab(page);
|
|
|
|
// Add a new endpoint
|
|
await page.locator(SELECTORS.addEndpointButton).click();
|
|
|
|
const dialog = page.locator(SELECTORS.dialog);
|
|
await expect(dialog).toBeVisible({ timeout: TIMEOUTS.dialogVisible });
|
|
|
|
// Fill in the endpoint form
|
|
const uniqueSuffix = Date.now();
|
|
await dialog.locator('input#endpoint-name').fill(`Test Endpoint ${uniqueSuffix}`);
|
|
await dialog.locator('input#endpoint-server').fill('https://ntfy.sh');
|
|
await dialog.locator('input#endpoint-topic').fill(`test-topic-${uniqueSuffix}`);
|
|
|
|
// Save the endpoint
|
|
const addButton = dialog.locator('button:has-text("Add Endpoint")').last();
|
|
await addButton.click();
|
|
|
|
// Wait for the success toast to confirm the save completed (including API call)
|
|
await waitForSuccessToast(page, 'Endpoint added', { timeout: 10000 });
|
|
|
|
// Dialog should close
|
|
await expect(dialog).toBeHidden({ timeout: TIMEOUTS.dialogHidden });
|
|
|
|
// Wait for the endpoint to appear in the list
|
|
await expect(page.locator(`text=Test Endpoint ${uniqueSuffix}`)).toBeVisible({
|
|
timeout: TIMEOUTS.endpointVisible,
|
|
});
|
|
|
|
// Reload the page
|
|
await page.reload();
|
|
|
|
// Re-authenticate after reload
|
|
await authenticateForTests(page);
|
|
|
|
// Navigate back to the endpoints tab
|
|
await navigateToEndpointsTab(page);
|
|
|
|
// Verify the endpoint persisted after reload
|
|
await expect(page.locator(`text=Test Endpoint ${uniqueSuffix}`)).toBeVisible({
|
|
timeout: TIMEOUTS.sectionVisible,
|
|
});
|
|
});
|
|
|
|
test('should display existing endpoints on initial load', async ({ page }) => {
|
|
// This test verifies that any existing endpoints are displayed when the page first loads
|
|
// Navigate to the page and check if we can see the endpoints section
|
|
|
|
await navigateToEndpointsTab(page);
|
|
|
|
// The endpoints tab should show either existing endpoints or the empty state
|
|
// The key is that it should NOT show "empty" if there are endpoints on the server
|
|
|
|
// Either we see "No ntfy endpoints configured" OR we see endpoint cards
|
|
const emptyState = page.locator('text=No ntfy endpoints configured');
|
|
const endpointCard = page.locator('[data-testid="endpoint-card"]').first();
|
|
|
|
// One of these should be visible (use Playwright's .or() to match either locator)
|
|
await expect(emptyState.or(endpointCard)).toBeVisible({
|
|
timeout: TIMEOUTS.endpointVisible,
|
|
});
|
|
});
|
|
});
|