diff --git a/.env.example b/.env.example index 7663c9be..1725b318 100644 --- a/.env.example +++ b/.env.example @@ -1 +1,4 @@ VITE_PARSEABLE_URL="https://demo.parseable.com" +VITE_USE_BASIC_AUTH=true +VITE_USERNAME=admin +VITE_PASSWORD=admin \ No newline at end of file diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index e6e1d02f..96eb75f9 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -17,11 +17,6 @@ jobs: run: npm install -g pnpm && pnpm install - name: Install Playwright Browsers run: pnpm exec playwright install --with-deps - - name: Start the development server - run: pnpm run dev & - env: - PORT: 3001 - VITE_PARSEABLE_URL: 'https://demo.parseable.com' - name: Run Playwright tests run: pnpm exec playwright test - uses: actions/upload-artifact@v4 diff --git a/playwright.config.ts b/playwright.config.ts index c607f68f..5c90d80b 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -2,8 +2,9 @@ import { defineConfig, devices } from '@playwright/test'; export default defineConfig({ testDir: './tests', + testIgnore: '**/login.spec.ts', /* Run tests in files in parallel */ - fullyParallel: true, + fullyParallel: true, // Set this to false to ensure sequential execution of files /* Fail the build on CI if you accidentally left test.only in the source code. */ forbidOnly: !!process.env.CI, /* Retry on CI only */ @@ -11,7 +12,7 @@ export default defineConfig({ /* 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', + reporter: 'line', /* 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('/')`. */ @@ -27,22 +28,27 @@ export default defineConfig({ name: 'chromium', use: { ...devices['Desktop Chrome'] }, }, - - { - name: 'firefox', - use: { ...devices['Desktop Firefox'] }, - }, - - { - name: 'webkit', - use: { ...devices['Desktop Safari'] }, - }, + // { + // name: 'Firefox', + // use: { ...devices['Desktop Firefox'] }, + // }, + // { + // name: 'Safari', + // use: { ...devices['Desktop Safari'] }, + // }, ], /* 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, - // }, + webServer: { + command: 'pnpm run dev', + url: 'http://localhost:3001', + reuseExistingServer: false, + env: { + PORT: '3001', + VITE_PARSEABLE_URL: 'https://demo.parseable.com', + VITE_USE_BASIC_AUTH: 'true', + VITE_USERNAME: 'admin', + VITE_PASSWORD: 'admin', + }, + }, }); diff --git a/src/pages/Stream/components/EventTimeLineGraph.tsx b/src/pages/Stream/components/EventTimeLineGraph.tsx index aafc6fe1..51d56b0c 100644 --- a/src/pages/Stream/components/EventTimeLineGraph.tsx +++ b/src/pages/Stream/components/EventTimeLineGraph.tsx @@ -20,7 +20,7 @@ const { makeTimeRangeLabel } = timeRangeUtils; type CompactInterval = 'minute' | 'day' | 'hour' | 'quarter-hour' | 'half-hour' | 'month'; function extractWhereClause(sql: string) { - const whereClauseRegex = /WHERE\s+(.*?)(?=\s*(ORDER\s+BY|GROUP\s+BY|LIMIT|$))/i; + const whereClauseRegex = /WHERE\s+(.*?)(?=\s*(ORDER\s+BY|GROUP\s+BY|OFFSET|LIMIT|$))/i; const match = sql.match(whereClauseRegex); if (match) { return match[1].trim(); diff --git a/src/pages/Stream/components/PrimaryToolbar.tsx b/src/pages/Stream/components/PrimaryToolbar.tsx index 3e67bb07..16e41568 100644 --- a/src/pages/Stream/components/PrimaryToolbar.tsx +++ b/src/pages/Stream/components/PrimaryToolbar.tsx @@ -21,7 +21,7 @@ import classes from './styles/PrimaryToolbar.module.css'; const { toggleDeleteModal, onToggleView } = logsStoreReducers; const { toggleSavedFiltersModal } = filterStoreReducers; const renderMaximizeIcon = () => ; -const renderDeleteIcon = () => ; +const renderDeleteIcon = () => ; const MaximizeButton = () => { const [_appStore, setAppStore] = useAppStore((_store) => null); diff --git a/src/pages/Stream/components/Sidebar.tsx b/src/pages/Stream/components/Sidebar.tsx index 7fdf1ca9..c94ab723 100644 --- a/src/pages/Stream/components/Sidebar.tsx +++ b/src/pages/Stream/components/Sidebar.tsx @@ -43,7 +43,8 @@ const ConfigButton = (props: MenuItemProps) => { props.setCurrentView(viewName)} style={{ padding: '4px 0', alignItems: 'center' }} - className={classes.menuItemContainer}> + className={classes.menuItemContainer} + data-id="manage-stream-btn"> { + test.beforeEach(async ({ page }) => { + await page.goto(`${TEST_URL}`); + }); + test('should render the component correctly with default state', async ({ page }) => { + await expect(page.locator('text=All Streams')).toBeVisible(); + await expect(page.locator('input[placeholder="Search Stream"]')).toBeVisible(); + }); + + test('should render the component correctly with streams data', async ({ page }) => { + // Check that the streams are displayed + await expect(page.locator('text=backend')).toBeVisible(); + await expect(page.locator('text=frontend')).toBeVisible(); + }); + + test('should filter streams based on search input', async ({ page }) => { + const searchInput = page.locator('input[placeholder="Search Stream"]'); + await searchInput.fill('backend'); + await expect(page.locator('text=backend')).toBeVisible(); + + // Clear the search and check if both streams are visible + await searchInput.fill(''); + await expect(page.locator('text=backend')).toBeVisible(); + await expect(page.locator('text=frontend')).toBeVisible(); + }); + + test('should display empty state if no streams are available', async ({ page }) => { + const searchInput = page.locator('input[placeholder="Search Stream"]'); + await searchInput.fill(Math.random().toString()); + + // Expect empty state view to be visible + await expect(page.locator('text=No Stream found on this account')).toBeVisible(); + await expect(page.locator('text=All Streams (0)')).toBeVisible(); + }); + + test('should display the create stream button', async ({ page }) => { + const createButton = page.locator('text=Create Stream'); + await expect(createButton).toBeVisible(); + }); + + test('should display the create stream modal on click', async ({ page }) => { + const createButton = page.locator('text=Create Stream'); + await expect(createButton).toBeVisible(); + await createButton.click(); + await expect(page.locator('text=Create Stream').nth(1)).toBeVisible(); + }); + + test.describe('Create Stream Modal', () => { + test.beforeEach(async ({ page }) => { + const createButton = page.locator('text=Create Stream'); + await createButton.click(); + }); + + test('should render the form correctly', async ({ page }) => { + // Check if essential elements are present in the form + await expect(page.locator('input[placeholder="Name"]')).toBeVisible(); + await expect(page.locator('text=Schema Type')).toBeVisible(); + await expect(page.locator('button:has-text("Create")').nth(1)).toBeVisible(); + }); + + test('should enable the submit button when form is valid', async ({ page }) => { + // Fill in the form with valid values + await page.fill('input[placeholder="Name"]', 'PlaywrightStream'); + + await page.locator('button:has-text("Create")').nth(1).click(); + await page.waitForTimeout(1000); + }); + + test.describe('Delete Stream', () => { + test('search, navigate, and delete demo stream', async ({ page }) => { + // Step 1: Search for the demo stream + await page.goto(`${TEST_URL}`); + const searchInput = page.locator('input[placeholder="Search Stream"]'); + await searchInput.fill('PlaywrightStream'); + await expect(page.locator('text=PlaywrightStream')).toBeVisible(); + + // Step 2: Navigate to the demo stream + await page.locator('text=PlaywrightStream').click(); + const manageBtn = page.locator('[data-id="manage-stream-btn"]'); + await expect(manageBtn).toBeVisible(); + await manageBtn.click(); + + await page.waitForTimeout(1000); + + // Step 3: Delete the demo stream + const deleteBtn = page.locator('[data-id="delete-stream-btn"]'); + await expect(deleteBtn).toBeVisible(); + await deleteBtn.click(); + await expect(page.locator('text=Delete Stream')).toBeVisible(); + await page.fill('input[placeholder*="Type the name of the stream to confirm. i.e."]', 'PlaywrightStream'); + const confirmDeleteBtn = page.getByRole('button', { name: 'Delete' }); + await expect(confirmDeleteBtn).toBeEnabled(); + await confirmDeleteBtn.click(); + + await page.waitForTimeout(1000); + + // Step 4: Check if stream is deleted + await page.goto(`${TEST_URL}`); + await expect(searchInput).toBeVisible(); + await searchInput.fill('PlaywrightStream'); + await expect(page.locator('text=No Stream found on this account')).toBeVisible(); + await expect(page.locator('text=All Streams (0)')).toBeVisible(); + }); + }); + }); +});