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();
+ });
+ });
+ });
+});